<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<title>Blog Posts</title>
	<subtitle>Data scientist writing about Python, data analysis and research software.</subtitle>
	<link rel="self" type="application/atom+xml" href="https://malramsay.com/post/feed.xml"/>
  <link rel="alternate" type="text/html" href="https://malramsay.com/post/"/>
  
	<updated>2022-02-07T00:00:00+00:00</updated>
	
	<id>https://malramsay.com/post/feed.xml</id>
	<entry xml:lang="en">
		<title>What&#39;s the time Azure Synapse</title>
		<published>2022-02-07T00:00:00+00:00</published>
		<updated>2022-02-07T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/what-is-the-time-azure-synapse/"/>
		<id>https://malramsay.com/post/what-is-the-time-azure-synapse/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/what-is-the-time-azure-synapse/">&lt;p&gt;Azure Synapse is Microsoft&#39;s fancy data analytics platform
designed to handle all kinds of data analytics tasks.
Unfortunately, there are significant deficiencies
in how this platform handles datetime data and timezones.
Making it incredibly difficult to use Synpase
in conjunction with other tools within the Data Science ecosystem.&lt;/p&gt;
&lt;p&gt;Dealing with time and timezones is an incredibly difficult problem,
not at all helped by the 6 separate timezones currently in use on mainland Australia&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;.
However, despite all the complexity of dealing with time
and the corresponding timezones,
there are two main ways of storing and working with time information;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use a naive datetime that just includes the date and the time which we are going to call &lt;em&gt;Local Time&lt;/em&gt;, or&lt;/li&gt;
&lt;li&gt;use a timezone aware datetime that includes the date, time, and a timezone offset
which we are going to call &lt;em&gt;Instantaneous Time&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It is really important to note that these two types of times,
&lt;em&gt;Local Time&lt;/em&gt; and &lt;em&gt;Instantaneous Time&lt;/em&gt; are different representations,
and converting between them is an incredibly complex process
—talking to you daylight savings, &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.timeanddate.com/news/time/samoa-dateline.html&quot;&gt;Samoa&lt;/a&gt;, and all the other exceptions.
This conversion needs to be done carefully and deliberately,
that is, use a library specifically designed for handling the conversion
and trust that whoever spent years handling all the edge cases
knows far, far better than you.&lt;/p&gt;
&lt;p&gt;Both of these approaches have their use cases,
&lt;em&gt;Local Time&lt;/em&gt; is simpler and suited to collecting data locally,
for example most of our daily activities,
or monitoring cycling patterns across a bridge.
&lt;em&gt;Instantaneous Time&lt;/em&gt; is required for data collected remotely
where we need the additional context of where the time was taken
allowing the comparison of the instant the event occurred.
As such see plenty of use in computer systems,
and also when travelling by aeroplane.
The major difference between &lt;em&gt;Local Time&lt;/em&gt; and and &lt;em&gt;Instantaneous Time&lt;/em&gt;
is we can convert the timezone of &lt;em&gt;Instantaneous Time&lt;/em&gt;
without changing the instant at which the event occured,
allowing for the comparison and ordering of events across timezones.&lt;/p&gt;
&lt;p&gt;This ability to change the timezone of &lt;em&gt;Instantaneous Time&lt;/em&gt;
also gives us the ability to convert all the times to a standard timezone,
which makes for an easy comparison of events.
The standard timezone to use for this comparison is
Co-ordinated Universal Time (UTC), which has a timezone offset of +0.
Working with this standard timezone allows for
an important simplification of &lt;em&gt;Instantaneous Time&lt;/em&gt;.
Rather than storing the date, time, and timezone offset,
we can store just the date, the time in the UTC timezone,
and that we have an &lt;em&gt;Instantaneous Time&lt;/em&gt;.
This is how many programming languages handle timezones,
performing the conversion to a local time when displaying the data.
This is also the approach used by the parquet file format,
which natively supports a TIMESTAMP data type.
The parquet format is designed to hold columnar data,
that is data where all the values within a column have the same data type.
They will all be integers, strings, ...or TIMESTAMPs.
The metadata of a parquet file describes the contents,
including the name of each column and the type of data stored within it.
When the data type is TIMESTAMP,
there is a option to specify whether we are storing &lt;em&gt;Local Time&lt;/em&gt;, or &lt;em&gt;Instantaneous Time&lt;/em&gt;.
This flag that we set has the name &lt;code&gt;isAdjustedtoUTC&lt;/code&gt; documenting
whether we are using the trick of converting everything to UTC described above.&lt;/p&gt;
&lt;p&gt;The Parquet file format is the enabler of the Data Lake,
allowing software to read and write data to disk
in an efficient and portable way.
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.microsoft.com/en-us/azure/synapse-analytics/overview-what-is&quot;&gt;Azure Synapse&lt;/a&gt; is Microsoft&#39;s fancy serverless database,
specifically designed to work alongside a Data Lake.
So it makes sense that there is built in support
for working with Parquet files.&lt;/p&gt;
&lt;p&gt;Like the Parquet file format,
Synapse has different timestamp types for &lt;em&gt;Local Time&lt;/em&gt; and &lt;em&gt;Instantaneous Time&lt;/em&gt;.
The &lt;code&gt;datetime2&lt;/code&gt; type is used for &lt;em&gt;Local Time&lt;/em&gt;,
while the &lt;code&gt;datetimeoffset&lt;/code&gt; type is used for &lt;em&gt;Instantaneous Time&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately the handling of datetime data within synapse is significantly lacking.
When trying to read a &lt;em&gt;Local Time&lt;/em&gt; from a parquet file
into the &lt;code&gt;datetime2&lt;/code&gt; type Synapse gives us this really helpful error message&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Column &amp;#39;datetime&amp;#39; of type &amp;#39;DATETIME2&amp;#39; is not compatible with external data type&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39;Parquet physical type: INT64&amp;#39;, please try with &amp;#39;BIGINT&amp;#39;. File/External table&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;name: &amp;#39;local.parquet&amp;#39;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Under the covers, the TIMESTAMP within a Parquet file is an INT64 type,
counting the number of milliseconds &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; since 1 January 1970 (1970-01-01 00:00).
Even more confusing about this error message is that the documentation
for the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.microsoft.com/en-us/azure/synapse-analytics/sql/develop-openrowset&quot;&gt;OPENROWSET function&lt;/a&gt; which is how we are opening the file
clearly states that TIMESTAMP (MILLIS / MICROS) are converted to the &lt;code&gt;datetime2&lt;/code&gt; type.
You might think that there would be a function built into Synapse
to allow for the manual conversion of a TIMESTAMP to the internal &lt;code&gt;datetime2&lt;/code&gt; format,
however, there is absolutely no mention of it within the documentation.
So we can&#39;t even perform this conversion ourselves
without first implementing the function to perform that conversion.&lt;/p&gt;
&lt;p&gt;So if reading in a &lt;em&gt;Local Time&lt;/em&gt; doesn&#39;t work, how about an &lt;em&gt;Instantaneous Time&lt;/em&gt;?
When we reading the &lt;em&gt;Instantaneous Time&lt;/em&gt; using the same approach,
rather than the cryptic error message, the file reads with no problems.
So we can just use &lt;em&gt;Instantaneous Time&lt;/em&gt; and everything works right???&lt;/p&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;Remember how I mentioned we want to be really careful about the conversion
between &lt;em&gt;Instantaneous Time&lt;/em&gt; and &lt;em&gt;Local Time&lt;/em&gt;,
well here Synapse is completely ignoring our &lt;em&gt;Instantaneous Time&lt;/em&gt;
and just reading the data into its &lt;code&gt;datetime2&lt;/code&gt; type representing &lt;em&gt;Local Time&lt;/em&gt;.
The problem here is that other tools&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; actually pay attention to these details,
so the &#39;quick fix&#39; of flicking the switch to using the &lt;em&gt;Instantaneous Time&lt;/em&gt;
is going to cause far more problems with other tools using this data.
To solve this problem properly,
we need changes at both sides of the data pipeline.
Firstly, the &lt;em&gt;Local Time&lt;/em&gt; needs to be converted to &lt;em&gt;Instantaneous Time&lt;/em&gt;,
assuming we know the locale in which the times were collected.
We will need this locale at the end,
so better hope it is documented somewhere
(I know that might be asking too much).
If we have no idea trying UTC and hoping for the best
at least allows us to read the file,
even if it causes problems down the track.
With the data having a locale,
one of the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://arrow.apache.org/docs/python/timestamps.html&quot;&gt;Apache Arrow Parquet writers&lt;/a&gt;
will automatically convert the timezone to UTC upon writing the file.
Now when reading the file within Synapse
we need to do two conversions when selecting our timestamp column,
the first to convert Synapse&#39;s &lt;em&gt;Local Time&lt;/em&gt; to
an &lt;em&gt;Instantaneous Time&lt;/em&gt; in the UTC time zone,
followed by a conversion to the local timezone we identified earlier.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;SELECT&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; timestamp&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; AT&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; TIME&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ZONE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;UTC&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; AT&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; TIME&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ZONE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Cen. Australia Standard Time&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So how about doing the conversion manually.
Well it turns out this acutally isn&#39;t possible
with the tools available within Synapse.
There is no inbuilt method to convert from this TIMESTAMP
to the inbuilt date types.
Additionally,
if we want to try and do this conversion manually
the &lt;code&gt;DATEADD&lt;/code&gt; function within Synapse only supports a 32 bit signed integer,
of which the largest is 2^16-1 (~2.1 billion).
In the most optimistic case supported by the Parquet file,
that is, using milliseconds since 1970-01-01 00:00:00,
the only dates we can describe are the 25 days surrounding it.
Which really doesn&#39;t solve the problem here.&lt;/p&gt;
&lt;p&gt;What I find really unusual about this whole situation
is that this is definitely not a result of using a really old Parquet reader.
We can know this because there has been a change in how the
TIMESTAMP type is defined within the Parquet file.
The previous types had no notion of &lt;em&gt;Local Time&lt;/em&gt;
and were either annotated as &lt;code&gt;TIMESTAMP_MILLIS&lt;/code&gt; or &lt;code&gt;TIMESTAMP_MICROS&lt;/code&gt;.
For compatibility reasons these old types are still annotated on the files
with the filetype specifications noting&lt;/p&gt;
&lt;p&gt;Despite there is no exact corresponding ConvertedType for local timestamp semantic, in order to support forward compatibility with those libraries, which annotated their local timestamps with legacy TIMESTAMP_MICROS and TIMESTAMP_MILLIS annotation, Parquet writer implementation &lt;em&gt;must&lt;/em&gt; annotate local timestamps with legacy annotations too, as shown below.&lt;/p&gt;
&lt;p&gt;If the Parquet reader for Synapse was only looking at these legacy annotations
then both &lt;em&gt;Local Time&lt;/em&gt; and &lt;em&gt;Instantaneous Time&lt;/em&gt; would work
since they have the same type defined.
It is only in the newer file format definition
that there is anything differentiating &lt;em&gt;Local&lt;/em&gt; and &lt;em&gt;Instantaneous&lt;/em&gt; time.
So the new types are supported, but not properly
which in my opinion is worse than not supporting them at all.
To add even more confusion to the mix,
the type provided when trying to read the parquet timestamp
into a &lt;code&gt;datetimeoffset&lt;/code&gt; type annotates the type
in the Parquet file as &lt;code&gt;TIMESTAMP_MILLIS&lt;/code&gt;,
that is, using the legacy annotation.&lt;/p&gt;
&lt;p&gt;For Synapse to be a properly fulfil a role within a Data Science toolkit
Microsoft really needs to properly support TIMESTAMPs
from the Parquet file format.
Currently, Synapse is unsuitable as part of a data pipeline
that handles datetime data.
The lack of both inbuilt support for TIMESTAMPs
and the tools for individuals to support them
severly limits the usefullness of Synapse,
and makes it difficult to recommend
as part of a broader data analytics platform.&lt;/p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;/sup&gt;
&lt;p&gt;During summer these are: AEST, AEDT, ACDT, ACST, AWST, and Eucla.
There are also at least 4 more in use off mainland Australia.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;/sup&gt;
&lt;p&gt;Microseconds are also a supported option. And in newer versions of the
parquet file format, Nanoseconds are also supported.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;/sup&gt;
&lt;p&gt;For example, the entire python Data Science ecosystem.&lt;/p&gt;
&lt;/div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Installing Fedora 35 on Raspberry Pi 4</title>
		<published>2022-01-08T00:00:00+00:00</published>
		<updated>2022-01-08T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/installing-fedora-rpi4/"/>
		<id>https://malramsay.com/post/installing-fedora-rpi4/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/installing-fedora-rpi4/">&lt;p&gt;I have found the installation of Fedora on a Raspberry Pi to be
an incredibly difficult process,
with many of the approaches I have tried not working for some reason.
This documents the process that ended up working for me,
along with many of the approaches that didn&#39;t work.&lt;/p&gt;
&lt;h2 id=&quot;installation-process&quot;&gt;Installation Process&lt;a class=&quot;zola-anchor&quot; href=&quot;#installation-process&quot; aria-label=&quot;Anchor link for: installation-process&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Unlike standard hardware like a server or desktop,
there are a number of steps in the installation process.
This is because the Raspberry Pi doesn&#39;t come with a standard UEFI bios
we can to boot from to boot from an installer USB.
Normally this would allow us to boot from a USB stick to enable the installation,
however, on the Raspberry Pi we first need to install the UEFI firmware.&lt;/p&gt;
&lt;h3 id=&quot;standard-install&quot;&gt;Standard install&lt;a class=&quot;zola-anchor&quot; href=&quot;#standard-install&quot; aria-label=&quot;Anchor link for: standard-install&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The latest version of Fedora for the aarch64 architecture
used by the Raspberry Pi 4 is available on the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://getfedora.org/en/workstation/download/&quot;&gt;Fedora Downloads&lt;/a&gt; site.
There are two downloads for aarch64,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;an aarch64 DVD ISO, and&lt;/li&gt;
&lt;li&gt;an aarch64 raw image.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We need second of those, the aarch64 raw image.
The raw image can be installed on a MicroSD card
and &lt;em&gt;should&lt;/em&gt; provide a working installation of Fedora.
I was unable to get a functional installation using this approach,
however this should work.&lt;/p&gt;
&lt;p&gt;To get the raw image onto the MicroSD card we can use the &lt;code&gt;arm-image-installer&lt;/code&gt; utility,
which is available as part of the fedora distribution through &lt;code&gt;dnf&lt;/code&gt;.
Running&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;dnf&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; arm-image-installer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will ensure it is installed locally.
Once installed we can use it to configure the installation.&lt;/p&gt;
&lt;p&gt;To find the appropriate path of the MicroSD card,
we can run the command&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; fdisk&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;l&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and finding the drive matching the size of the MicroSD card.
For me this is &lt;code&gt;/dev/sda&lt;/code&gt; since I have an NVMe boot drive on my local computer.
Make sure to double check the drive path, since you don&#39;t want to overwrite your local boot drive.
With the appropriate drive path found, replace &lt;code&gt;&amp;lt;MicroSD Card&amp;gt;&lt;/code&gt; with the path.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; arm-image-installer&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-image=Downloads/Fedora-Workstation-35-1.2.armhfp.raw.xz&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-target=rpi4&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-media=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;MicroSD&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Car&lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-resizefs&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the image is the file we downloaded,&lt;/li&gt;
&lt;li&gt;the target is the device we want to use with the MicroSD card
in this case a Raspberry Pi 4 (rpi4).&lt;/li&gt;
&lt;li&gt;The media is where we are writing the data to&lt;/li&gt;
&lt;li&gt;and &lt;code&gt;--resizefs&lt;/code&gt; tells &lt;code&gt;arm-image-installer&lt;/code&gt; to expand the storage to fill the media,
making the entire drive usable when booted up.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once these steps are completed,
we can try running the drive on a Raspberry Pi.
Insert the drive, connect up the power and monitor,
and everything should boot up fine,
giving the initial configuration screen for Fedora.&lt;/p&gt;
&lt;p&gt;Unfortunately in my case, I was unable to go through the configuration,
with my keyboard unresponsive upon reaching the configuration screen.
Because I was unable to continue through the installation,
I had to take an alternative approach.&lt;/p&gt;
&lt;h3 id=&quot;installing-uefi-firmware&quot;&gt;Installing UEFI Firmware&lt;a class=&quot;zola-anchor&quot; href=&quot;#installing-uefi-firmware&quot; aria-label=&quot;Anchor link for: installing-uefi-firmware&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;With the raw image not working,
I had to take an alternate approach for the installation.
The alternative download to the RAW image was the DVD ISO,
so that was my next approach.
However, as mentioned earlier,
the Raspberry PI doesn&#39;t ship with the UEFI bootloader we need
to get the DVD ISO installed.
Fortunately however, we can download one.
The UEFI firmware that we need is available in the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/pftf/RPi4/releases/latest&quot;&gt;releases&lt;/a&gt;
of the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/pftf/RPi4&quot;&gt;pftf/RPi4&lt;/a&gt; GitHub repository.
The &lt;code&gt;.zip&lt;/code&gt; file should be downloaded onto your local machine.&lt;/p&gt;
&lt;p&gt;The instructions listed describe creating a FAT32 partition for the firmware
however, it just so happens that when we created the RAW image,
we have also created all the partitions we need for this step already.
Putting the SD card back into the computer that is working,
you should see three partitions on the drive.
A large one that close to the total capacity of the SD card,
which will be the root partition and is where all the Fedora OS is installed.
This will contain many folders including those named &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;etc&lt;/code&gt;, &lt;code&gt;home&lt;/code&gt;, &lt;code&gt;sys&lt;/code&gt;, and &lt;code&gt;proc&lt;/code&gt;.
There will be a drive about 1.1 GB which is where the OS Kernel is installed,
this will include folders like &lt;code&gt;dtb&lt;/code&gt;, &lt;code&gt;grub2&lt;/code&gt;, &lt;code&gt;loader&lt;/code&gt;.
The final directory is the one we want,
which appears for me as 629 MB in size;
containing a number of &lt;code&gt;.elf&lt;/code&gt; files.
The zip file with the UEFI firmware can be extracted to this drive,
replacing any of the files that are already there.&lt;/p&gt;
&lt;p&gt;With the bootloader installed onto the drive,
we now have a UEFI firmware.
This can be checked by inserting the MicroSD card
into the Raspberry Pi and booting it up.
It will now provide options for how you want to boot the Pi.&lt;/p&gt;
&lt;p&gt;While you are here,
it is a good idea to remove the 3GB memory limit.
This is imposed to ensure support for older kernels,
however using Fedora 35 is new enough to not cause any issues.&lt;/p&gt;
&lt;h3 id=&quot;installing-the-dvd-iso&quot;&gt;Installing the DVD ISO&lt;a class=&quot;zola-anchor&quot; href=&quot;#installing-the-dvd-iso&quot; aria-label=&quot;Anchor link for: installing-the-dvd-iso&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;To install the DVD image onto our Pi,
we first need to download the DVD ISO,
which is available at the same &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://getfedora.org/en/workstation/download/&quot;&gt;Fedora Downloads&lt;/a&gt; link as above.
However, this time we need the first link for the DVD ISO.&lt;/p&gt;
&lt;p&gt;The simplest method to get this ISO onto Fedora Workstation onto a USB
is to use the Fedora Media Writer utility,
which can be installed using &lt;code&gt;dnf&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;dnf&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; fedora-media-writer&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running Fedora Media Writer we need to select the downloaded image.&lt;/p&gt;
&lt;h2 id=&quot;why-all-this-effort&quot;&gt;Why all this Effort?&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-all-this-effort&quot; aria-label=&quot;Anchor link for: why-all-this-effort&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The key reason for choosing Fedora is because I am familiar with it
and the tooling and versions are consistent across the rest of my workspaces.
Additionally I have been playing around with Rust and want to compile my code
for the aarch64 architecture.
This means that the standard Raspberry Pi OS is not appropriate,
only being the 32 rather than 64 bit architecture.&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>A Workflow for Data Science</title>
		<published>2021-06-29T00:00:00+00:00</published>
		<updated>2021-06-29T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/workflow-for-data-science/"/>
		<id>https://malramsay.com/post/workflow-for-data-science/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/workflow-for-data-science/">&lt;p&gt;Jupyter notebooks are a phenomenal tool
for investigating and interacting with data.
The interactive nature of the notebooks
allows for quick and effective interrogation of the data
and more generally the prototyping of an idea.
By combining interaction, the documentation, the code,
and the visualisations,
the notebooks are also a fantastic tool for communicating ideas.
However, notebooks are not the most appropriate for all the code we write,
with many there being many detractors to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.youtube.com/watch?v=7jiPeIFXb6U&quot;&gt;using notebooks&lt;/a&gt;
and there are definitely valid reasons for disliking the notebook.
However, most of the issues can be addressed by recognising that
the jupyter notebook is one of many tools that we have available,
and knowing when to move beyond the notebook
can help us make the most of it.&lt;/p&gt;
&lt;p&gt;While I am using python to illustrate these steps and examples,
the same ideas apply for any language being used in the notebook
just with some slightly different specifics on the how.&lt;/p&gt;
&lt;h2 id=&quot;idea-development&quot;&gt;Idea Development&lt;a class=&quot;zola-anchor&quot; href=&quot;#idea-development&quot; aria-label=&quot;Anchor link for: idea-development&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The first step within the process is the exploration and development of an idea.
This is where we are just getting started looking at a problem
and we probably haven&#39;t written any code yet.
In this phase of development we want to make full use
of the interactivity of jupyter.
This is where we can easily play around
to make sure we are using the right options for reading in our file,
trying lots of different types of analysis,
while having a record of what we have done
so that we can easily regenerate a previous state when we break something.&lt;/p&gt;
&lt;p&gt;It is here where the interactive nature of the juptyer notebook really stands out.
It is easy to introspect the state at each step in the process,
looking at the problem in different ways to properly understand what is going on.
We also have the visualisations,
which allow us to generate nice figures representing the data,
however also includes the pretty printed outputs,
like the nicely formatted tables of dataframes,
which make working with the data easier.&lt;/p&gt;
&lt;p&gt;The way we retain state within the notebook
is incredibly appealing,
particularly where the first step of the process is time consuming,
like loading the data from a URL,
performing a complex database query,
or doing some intermediate processing.&lt;/p&gt;
&lt;h2 id=&quot;organisation-of-thoughts&quot;&gt;Organisation of Thoughts&lt;a class=&quot;zola-anchor&quot; href=&quot;#organisation-of-thoughts&quot; aria-label=&quot;Anchor link for: organisation-of-thoughts&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;As you start to get an understanding of the problem you are solving
and the types of analyses and steps that you will be performing.
You are going to start grouping cells together,
to make it easier to run.
Reading the dataset and the initial transformations in one cell,
another to calculate some interesting values,
and one to generate a visualisation.&lt;/p&gt;
&lt;p&gt;This process of organising related lines of code together
is the first step towards looking at creating functions.
The functions allow you to abstract away the details
of what is going on in a particular step,
like the data loading, and focus on the important parts.&lt;/p&gt;
&lt;p&gt;We might have the following code to read in a file&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;df&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; pd&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;read_csv&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;data/gapminder_data.csv&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;df&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;country&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; df&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;country&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;astype&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;df&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;continent&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; df&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;continent&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;astype&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where the datafile &lt;code&gt;gapminder_data.csv&lt;/code&gt; is from the software carpentry training courses
and can be downloaded &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://raw.githubusercontent.com/swcarpentry/r-novice-gapminder/gh-pages/_episodes_rmd/data/gapminder_data.csv&quot;&gt;here&lt;/a&gt;.
In loading this file we are concerned with two inputs,
firstly, the name of the file that we are loading,
and secondly the names of the fields
we want to load as categorical columns.
This results in a dataframe that is ready for us to use.
The function we might write could look something like what we have below,
where I have used type annotations to be clear about the data passed as input.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; read_csv_dataset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;filename&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; categorical_columns&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; List&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;str&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  df&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; pd&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;read_csv&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;data/gapminder_data.csv&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  for&lt;/span&gt;&lt;span&gt; column&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span&gt; categorical_columns&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    df&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;column&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; df&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;column&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;astype&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  return&lt;/span&gt;&lt;span&gt; df&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By transforming our code snippet into a function,
we can make our intent clearer within the narrative of the notebook.
Now when we load the dataset we can use the line&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;df&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; read_csv_dataset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;  filename&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;data/gapminder_data.csv&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;  categorical_columns&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;country&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;continent&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which makes it clear to the reader that we want the &lt;code&gt;country&lt;/code&gt; and &lt;code&gt;continent&lt;/code&gt; columns to be categorical
while everything else is less important to our analysis.
This function can then be re-used within a notebook,
serving slightly different purposes each time,
loading a different file,
or allowing us to investigate whether setting columns to categorical
changes the performance of our analysis.&lt;/p&gt;
&lt;h2 id=&quot;solidification-of-idea&quot;&gt;Solidification of Idea&lt;a class=&quot;zola-anchor&quot; href=&quot;#solidification-of-idea&quot; aria-label=&quot;Anchor link for: solidification-of-idea&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Once we have defined a function within one notebook,
it often becomes something we want to reach for within other notebooks.
Alternatively the definition of the function
could be detracting from the story we are trying to tell within the notebook.
Once we have solidified an idea and defined a function,
we can move that function to a &lt;code&gt;.py&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;By creating a file &lt;code&gt;loading.py&lt;/code&gt; in the same directory as the jupyter notebook
with the &lt;code&gt;read_csv_dataset&lt;/code&gt; function as its contents&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; loading.py&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; pandas&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; as&lt;/span&gt;&lt;span&gt; pd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; read_csv_dataset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;filename&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; categorical_columns&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; List&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;str&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  df&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; pd&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;read_csv&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;data/gapminder_data.csv&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  for&lt;/span&gt;&lt;span&gt; column&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span&gt; categorical_columns&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    df&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;column&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; df&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;column&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;astype&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;  return&lt;/span&gt;&lt;span&gt; df&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;we can import this function into the notebook as follows&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;from loading import read_csv_dataset&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works because the file loading is in the same directory as the juptyer notebook,
if it was located elsewhere, we would need to modify where jupyter looks for files to import.
I will commonly locate all my notebooks within a &lt;code&gt;notebook&lt;/code&gt; directory of a project,
while all the python files in the &lt;code&gt;src&lt;/code&gt; directory of a project,
a structure like below.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;project/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── notebook&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── gapminder.ipynb&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└── src&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    └── loading.py&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To import the &lt;code&gt;loading.py&lt;/code&gt; file (in this context it is called a module) from the &lt;code&gt;gapminder.ipynb&lt;/code&gt; notebook,
the simplest method is to tell python to look in that &lt;code&gt;src&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;If we change the import to&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;import sys&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sys.path.append(&amp;quot;../src&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;from loading import read_csv_dataset&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where the &lt;code&gt;sys&lt;/code&gt; module is one of python&#39;s built in modules
and &lt;code&gt;sys.path&lt;/code&gt; is the list of places that python looks
for things to import.
By manually adding the relative path to the &lt;code&gt;src&lt;/code&gt; directory
we are able to import from any files defined within it.&lt;/p&gt;
&lt;p&gt;One disadvantage of the move to importing from a file
is that it becomes harder to update the function,
requiring a restart of the kernel to reload the module.
This can be worked around by using the &lt;code&gt;%autoreload&lt;/code&gt; magic
built into IPython.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;%load_ext autoreload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;%autoreload 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;import sys&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sys.path.append(&amp;quot;../src&amp;quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;from loading import read_csv_dataset&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which means that any time we edit the file &lt;code&gt;../src/loading.py&lt;/code&gt;,
python will reload the file and its definitions
replacing the previous version of &lt;code&gt;read_csv_dataset&lt;/code&gt; with the new one.&lt;/p&gt;
&lt;h2 id=&quot;does-this-code-actually-work&quot;&gt;Does this code actually work&lt;a class=&quot;zola-anchor&quot; href=&quot;#does-this-code-actually-work&quot; aria-label=&quot;Anchor link for: does-this-code-actually-work&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Once we have moved code from a jupyter notebook to a python file
we have a range of tools available to help evaluate whether
the code we have written works.
Within the notebook this would normally be a quick process of running the code,
within a python file this can become a little harder,
particularly as the size of the file gets larger.&lt;/p&gt;
&lt;p&gt;To help with this there are a number of tools making this process easier,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;black&lt;/code&gt; provides automatic code formatting so you can worry about the code
rather than how it is laid out.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mypy&lt;/code&gt; provides static type checking, which can be really useful for finding
all the places &lt;code&gt;None&lt;/code&gt; can show up in the code. Note that this only applies if
you are using type annotations.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flake8&lt;/code&gt; provides a range of checks that the code works, primarily checking
for common mistakes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these tools however actually run the code,
for that we want to look towards &lt;code&gt;pytest&lt;/code&gt;
providing tools to run the code to make sure it works.&lt;/p&gt;
&lt;h2 id=&quot;package-creation&quot;&gt;Package Creation&lt;a class=&quot;zola-anchor&quot; href=&quot;#package-creation&quot; aria-label=&quot;Anchor link for: package-creation&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;In the vast majority of data analysis projects,
having the code in a separate file is as far as we need to go.
All the tooling is going to work in helping us keep our code working.
However, there are some cases where the functionality you are working on
is more widely applicable and needed in multiple projects.&lt;/p&gt;
&lt;p&gt;For this we can create a package for the code,
with the package becoming a dependency of the project.
This allows for anyone to install the package
and we can use the same tools &lt;code&gt;pip&lt;/code&gt; and/or &lt;code&gt;conda&lt;/code&gt;
that we use to manage the rest of our external dependencies.
For information on packaging the Python Package Authority (PyPA)
has a useful &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://packaging.python.org/&quot;&gt;user guide&lt;/a&gt;
and conda also has a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.conda.io/projects/conda-build/en/latest/user-guide/tutorials/index.html&quot;&gt;user guide&lt;/a&gt; for getting started.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The Jupyter notebook is the starting place for code,
it allows us to quickly iterate and try ideas out to see what works.
Additionally notebooks they are a fantastic storytelling tool,
however to make the most of them and to keep the story engaging,
we sometimes need to look beyond the notebook.
How far we go depends heavily on the type of project being undertaken
and the tools we want to use.&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Updating Query with Soft Delete for SQLAlchemy 1.4</title>
		<published>2021-03-31T00:00:00+00:00</published>
		<updated>2021-03-31T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/updating-query-with-soft-delete/"/>
		<id>https://malramsay.com/post/updating-query-with-soft-delete/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/updating-query-with-soft-delete/">&lt;p&gt;When working on a database,
there are times where we want to delete a row,
but at the same time we still want to keep around
the record that the particular row was referring to.
There can be many reasons for this,
however the basic idea is that we want the row to generally be hidden.
The Soft Delete pattern allows for the &#39;delete&#39; operation,
to hide the row from the external interface,
while it remains within the database.
Miguel Grinberg has a great writeup of the pattern
and implementation in SQLAlchemy on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://blog.miguelgrinberg.com/post/implementing-the-soft-delete-pattern-with-flask-and-sqlalchemy&quot;&gt;their blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This implementation is great,
however the SQLAlchemy 1.4 release changed some of the internals
meaning Miguel&#39;s implementation no longer works,
giving the below error&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;AttributeError&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;QueryWithSoftDelete&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; object&lt;/span&gt;&lt;span&gt; has&lt;/span&gt;&lt;span&gt; no&lt;/span&gt;&lt;span&gt; attribute&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;_mapper_zero&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The fix for this issue is to replace the &lt;code&gt;_mapper_zero&lt;/code&gt; method with the &lt;code&gt;_entity_from_pre_ent_zero&lt;/code&gt; method, giving a &lt;code&gt;with_deleted&lt;/code&gt; method as below.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; QueryWithSoftDelete&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;BaseQuery&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; with_deleted&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;__class__&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;class_mapper&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_entity_from_pre_ent_zero&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;class_&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;            session&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;            _with_deleted&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;why-does-this-work&quot;&gt;Why does this work?&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-does-this-work&quot; aria-label=&quot;Anchor link for: why-does-this-work&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;With the fix out of the way,
we can have a look at what the &lt;code&gt;with_deleted&lt;/code&gt; method is doing.
The entire code snippet for the &lt;code&gt;QueryWithSoftDelete&lt;/code&gt; is reproduced below for reference.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; QueryWithSoftDelete&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;BaseQuery&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    def&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; __new__&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cls&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; **&lt;/span&gt;&lt;span&gt;kwargs&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        obj&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; super&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;QueryWithSoftDelete&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; cls&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;__new__&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;cls&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        with_deleted&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; kwargs&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;_with_deleted&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        if&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; len&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;            super&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;QueryWithSoftDelete&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; obj&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;__init__&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; **&lt;/span&gt;&lt;span&gt;kwargs&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;            return&lt;/span&gt;&lt;span&gt; obj&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;filter_by&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;deleted&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; if&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; not&lt;/span&gt;&lt;span&gt; with_deleted&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; else&lt;/span&gt;&lt;span&gt; obj&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        return&lt;/span&gt;&lt;span&gt; obj&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    def&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; __init__&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; **&lt;/span&gt;&lt;span&gt;kwargs&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        pass&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; with_deleted&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;__class__&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;class_mapper&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;_entity_from_pre_ent_zero&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;class_&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;            session&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;            _with_deleted&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        )&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;with_deleted&lt;/code&gt; method provides a way to transform the Query
from one that filters out deleted values to one that does.
Now there isn&#39;t a straightforward method for removing filters from a query,
so the approach taken here is to create a new Query,
this time setting &lt;code&gt;_with_deleted=True&lt;/code&gt;.
The problem now becomes,
which model are we trying to query?
When we call the &lt;code&gt;with_deleted&lt;/code&gt; method,
we are doing so from an instance of the &lt;code&gt;QueryWithSoftDelete&lt;/code&gt; class,
so we are unable to extract the model being used through
methods like &lt;code&gt;__class__&lt;/code&gt;, it gives us the &lt;code&gt;QueryWithSoftDelete&lt;/code&gt; class.
So instead we can look to SQLAlchemy to get this information.
Within SQLAlchemy, every query from the database needs a &lt;code&gt;FROM&lt;/code&gt; clause,
the table that we are going to be using to initially select our data.
This table we are selecting,
is the same one that we want to query
though this time instead of filtering the deleted items,
we will be including them.&lt;/p&gt;
&lt;p&gt;Previously the &lt;code&gt;_mapper_zero&lt;/code&gt; method provided that table in the &lt;code&gt;FROM&lt;/code&gt; clause.
However the restructuring of the Query to consolidate the core and ORM components
meant the method disappeared,
completely fine given it is a &#39;hidden&#39; method.
We need the function that will give the equivalent behaviour,
which happens to be the much more verbose, &lt;code&gt;_entitiy_from_pre_ent_zero&lt;/code&gt;.&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Handling Exceptions in Numerical Python</title>
		<published>2020-10-19T00:00:00+00:00</published>
		<updated>2020-10-19T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/handling-exceptions-in-python/"/>
		<id>https://malramsay.com/post/handling-exceptions-in-python/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/handling-exceptions-in-python/">&lt;p&gt;The numerical computing capabilities of the Python ecosystem are incredibly powerful,
with the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://numpy.org/&quot;&gt;Numpy&lt;/a&gt; and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.scipy.org/&quot;&gt;Scipy&lt;/a&gt;, and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://pandas.pydata.org/&quot;&gt;Pandas&lt;/a&gt; packages
providing high performance, well tested tools.
Additionally, tools like &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://dask.org/&quot;&gt;Dask&lt;/a&gt; and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://rapids.ai/&quot;&gt;Rapids&lt;/a&gt;
build upon these foundational packages supporting larger datasets.
All these tools are excellent
when the data is well formed and contains the expected values.
It is when we are handling the error cases
that we can run into issues.&lt;/p&gt;
&lt;p&gt;During my PhD,
I used scipy for the analysis of molecular dynamics simulations.
These simulations are effectively putting things in a box
and shaking it to see what happens.
The novel component of my research
was shaking the box for a really, really, really, long time.
So long that the software I was using ran out of numbers to count. &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;
Having run the simulation,
I then needed to go though the terabytes of data generated
to calculate quantities of interest.&lt;/p&gt;
&lt;p&gt;The huge benefit of using python and the suite of numerical tools
is the ability to quickly prototype a solution,
working through the ideas on a small scale
that can then be applied to the larger dataset.
One of the calculations I was performing
was to fit a straight line to a region of calculated values. &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;
We could come up with the following solution
for fitting line of best fit to a region of a curve,
returning both the best parameters and the standard deviation.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; scipy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; linear&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; m&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Define the linear relationship y=mx+b.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    return&lt;/span&gt;&lt;span&gt; m&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt; x&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; +&lt;/span&gt;&lt;span&gt; b&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; fit_linear_region&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Fit a linear curve where the points lie within a region of 2D space.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Select a region of points&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    region&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; np&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;logical_and&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 50&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Perform the curve fitting only using the linear region&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    popt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; pcov&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; scipy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;optimize&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;curve_fit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;linear&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; x&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;region&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;region&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Calculate the standard deviation of the fit parameters&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    perr&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt; np&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sqrt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;np&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;diag&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pcov&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Returns the optimal curve and associated error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    return&lt;/span&gt;&lt;span&gt; popt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; perr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When performing tests on small numbers of values,
we typically choose those behaving as we expect.
Over the course of many long simulations
nearly all possible values are expressed,
meaning this initial testing only covers a small selection
of all the possible values.
On many occasions,
these unexpected values resulted in the code raising an exception,
unexpectedly bringing an hours long calculation to a halt.
For interactive analysis,
these exceptions are not too problematic,
you can easily fix the problem and re-run the analysis.
However when leaving the calculation running overnight,
coming back to halted execution partway through the analysis
is incredibly frustrating.
In these long running analyses,
in moving from the prototyping
to the production step of the process,
we have to consider all the places
where our code might fail
allowing us to walk away knowing a result
will be waiting for us the next morning.&lt;/p&gt;
&lt;h2 id=&quot;exploring-the-solution-space&quot;&gt;Exploring the Solution Space&lt;a class=&quot;zola-anchor&quot; href=&quot;#exploring-the-solution-space&quot; aria-label=&quot;Anchor link for: exploring-the-solution-space&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The problem of handling errors within code
is faced by programmers in a wide range of disciplines.
Accordingly, there are a range of approaches
to ensure our code doesn&#39;t unexpectedly fail.&lt;/p&gt;
&lt;h3 id=&quot;catch-all&quot;&gt;Catch-all&lt;a class=&quot;zola-anchor&quot; href=&quot;#catch-all&quot; aria-label=&quot;Anchor link for: catch-all&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;One of the possible approaches we can take
is to capture all exceptions with a naked &lt;code&gt;except&lt;/code&gt; block,
that is&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;try&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    popt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; pcov&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; scipy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;optimize&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;curve_fit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;linear&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; x&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;region&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;region&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; Exception&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    return&lt;/span&gt;&lt;span&gt; np&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;nan&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; np&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;nan&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this approach handles all possible issues that could occur,
it is also heavy handed in its application.
The general goal is to prevent unexpected errors
in a particular configuration halting the execution of our program.
However, in capturing all possible errors,
we also capture errors that can be problematic.
For example a &lt;code&gt;KeyboardInterrupt&lt;/code&gt;,
meaning that rather than &lt;code&gt;&amp;lt;Ctrl&amp;gt;-c&lt;/code&gt; stopping the execution of the program,
it instead describes a curve that doesn&#39;t fit.
This could also be problematic with a &lt;code&gt;FileNotFoundError&lt;/code&gt;,
where instead of getting an error at the start of the program
we have to work out why our results file doesn&#39;t exist
when trying to look through them.&lt;/p&gt;
&lt;p&gt;While the idea of the catch-all is useful,
it is primarily delaying problems identified when the program runs
to subsequent steps in analysis,
which become more difficult to solve,
amplified by the lack of error messages
and tracebacks that python provides.&lt;/p&gt;
&lt;h3 id=&quot;hypothesis&quot;&gt;Hypothesis&lt;a class=&quot;zola-anchor&quot; href=&quot;#hypothesis&quot; aria-label=&quot;Anchor link for: hypothesis&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The main issue with the catch-all case
is that we are overly broad
in the types of exceptions that we catch.
Rather than catching all exceptions,
what if we could determine the exceptions expected from a function call,
only catching that smaller subset.
This is similar to the approach that you would take
in an interactive type analysis
where you keep adding the exceptions that arise
to the list of those caught and handled.
An alternative to finding these exceptions manually
is through [hypothesis],
a package for enhancing test suites by
using a directed random search,
probing for values that cause problems.
The directed part of hypothesis
ensures values likely to cause issues,
including small values, large values, zero, NaN, and Infinities
are handled appropriately.
In ensuring we handle all these different values,
Hypothesis is a tool that ensures that we codify
the assumptions we make about the data we put into our algorithms.&lt;/p&gt;
&lt;p&gt;Working with hypothesis requires a fair amount of setup,
building upon a test framework like pytest.
Additionally, as a property testing framework,
rather than testing specific values
we instead need to test properties,
requires a rethinking of how we test functions.
In the example covered in the this article,
the property we are testing is ensuring our function
is doesn&#39;t unexpectedly raise an exception.
The testing of this property
can be achieved by creating the test
as shown in the code snippet below.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;from&lt;/span&gt;&lt;span&gt; hypothesis&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt; given&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;from&lt;/span&gt;&lt;span&gt; hypothesis&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;extra&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;numpy&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt; arrays&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;given&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;arrays&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;dtype&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;float&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; shape&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; y&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;arrays&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;dtype&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;float&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; shape&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; test_example&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    fit_linear_region&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we use hypothesis to generate the two inputs to our function, &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;.
Each of these is created from a numpy array of 10 elements (&lt;code&gt;shape=10&lt;/code&gt;)
containing floating point values (&lt;code&gt;dtype=float&lt;/code&gt;).
The &lt;code&gt;@given&lt;/code&gt; decorator allows hypothesis to generate inputs
based on the values that we have provided,
running each to check that it runs.
In this code snippet
we are making clear some assumptions that we have made about our data.
Firstly that we are expecting floating point values,
being the &lt;code&gt;dtype&lt;/code&gt; that we are generating.
The second is that the &lt;code&gt;shape&lt;/code&gt; of both &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; is equal.
One of the advantages of hypothesis
is bringing to light these occasionally hidden assumptions
about the inputs to our functions.&lt;/p&gt;
&lt;p&gt;By using hypothesis to generate inputs to our function,
we find some additional errors that can occur.
When there are no points that lie within our specified range,
we get a &lt;code&gt;ValueError&lt;/code&gt;,
while when there is only a single value in that range
we get a &lt;code&gt;TypeError&lt;/code&gt;.
Additionally, when scipy is unable to find
and appropriate line of best fit,
an &lt;code&gt;OptimizeWarning&lt;/code&gt; is raised.
This results in the following &lt;code&gt;fit_linear_region&lt;/code&gt; function
to handle these three exception cases.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; fit_linear_region&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Fit a linear curve where the points lie within a region of 2D space.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Select a region of points&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    region&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; np&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;logical_and&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 50&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Perform the curve fitting only using the linear region&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    try&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        popt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; pcov&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; scipy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;optimize&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;curve_fit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;linear&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; x&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;region&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; y&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;region&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    except&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;ValueError&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; TypeError&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; scipy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;optimize&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;OptimizeWarning&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;        return&lt;/span&gt;&lt;span&gt; np&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;nan&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; np&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;nan&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Calculate the standard deviation of the fit parameters&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    perr&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; *&lt;/span&gt;&lt;span&gt; np&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sqrt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;np&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;diag&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pcov&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Returns the optimal curve and associated error&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    return&lt;/span&gt;&lt;span&gt; popt&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; perr&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While using hypothesis allows us to easily find
some of the failure modes of functions,
it is another tool to learn how to use effectively.
It would be nicer if there was a simpler solution
to finding the errors raised by a function
that made use of existing tools.&lt;/p&gt;
&lt;h3 id=&quot;documentation&quot;&gt;Documentation&lt;a class=&quot;zola-anchor&quot; href=&quot;#documentation&quot; aria-label=&quot;Anchor link for: documentation&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The documentation for a function
is something regularly referred to,
whether that be through the online documentation
or through the help messages of a function.
This makes the function docstrings a fantastic place
to put information about the exceptions a function could raise.
The scipy docs include this information
for some of the functions, including &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html&quot;&gt;curve fit&lt;/a&gt;.
Within the &lt;strong&gt;Raises&lt;/strong&gt; section,
it notes there are three different exceptions the function can raise,
a &lt;code&gt;ValueError&lt;/code&gt;, &lt;code&gt;RuntimeError&lt;/code&gt;, or &lt;code&gt;OptimizeWarning&lt;/code&gt;.
This list of exceptions demonstrates a limitation of using hypothesis,
it won&#39;t always find &lt;em&gt;all&lt;/em&gt; the possible exceptions a function can raise,
here missing the &lt;code&gt;RuntimeError&lt;/code&gt;.
However, on the flip side,
the &lt;code&gt;TypeError&lt;/code&gt; we found with hypothesis is currently missing from the documentation.
This is a limitation of the documentation approach,
the exception comes from a function called by &lt;code&gt;curve_fit&lt;/code&gt;,
propagating beyond the initial call site,
making it difficult to notice and to keep the documentation up to date.
The documentation approach is also prone to degrading over time,
as the code gets updated and changed,
the documentation has to be changed and updated alongside it,
and for exceptions this can be in multiple unrelated locations.
One of the solutions for this
is representing the possible exceptions in code,
rather than through the documentation
allowing tooling to provide assistance,
much like &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://mypy-lang.org/&quot;&gt;mypy&lt;/a&gt; and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/microsoft/pyright&quot;&gt;pyright&lt;/a&gt; do for type annotations.&lt;/p&gt;
&lt;h3 id=&quot;rust&quot;&gt;Rust&lt;a class=&quot;zola-anchor&quot; href=&quot;#rust&quot; aria-label=&quot;Anchor link for: rust&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;One of the highly regarded parts of the Rust programming language,
is the comprehensive type system that explicitly handles errors.
In a similar way to exceptions within Python,
the &lt;code&gt;Result&lt;/code&gt; type within Rust
is a way of stating that something has gone wrong.
The big difference between an exception in python
and the &lt;code&gt;Result&lt;/code&gt; type in Rust,
is that in Rust you &lt;em&gt;must&lt;/em&gt; explicitly handle the error.
In the simplest case this error handling is equivalent to a python exception,
stopping the execution of the program and exiting.
The key enhancement with the Rust approach
is that there is a record of every place
within the code that an error could halt the execution.
When prototyping on the sample datasets within Rust,
we can use the exception model of error handling,
halting the execution of the program using &lt;code&gt;.unwrap()&lt;/code&gt;.
However, when moving to a more complex analysis pipeline,
or when developing a library that people rely on,
unexpected errors are no longer a good option.
Here, all instances of &lt;code&gt;.unwrap()&lt;/code&gt;
can be converted into more appropriate methods
to handle those errors.
These propagation of the &lt;code&gt;Result&lt;/code&gt; type occurs similarly to
the handling of &lt;code&gt;Optional&lt;/code&gt; values within type checked python code.
Whenever we call a function that returns a &lt;code&gt;Result&lt;/code&gt;,
we have to check whether we have the &lt;code&gt;Ok&lt;/code&gt; value,
in which case we can continue on,
or the &lt;code&gt;Err&lt;/code&gt; value,
where it has to be either be handled
or passed on to a calling function through the &lt;code&gt;Result&lt;/code&gt; type.&lt;/p&gt;
&lt;p&gt;In principle, supporting a &lt;code&gt;Result&lt;/code&gt; type within python
like exists within Rust is possible, however,
what makes the type so useful within Rust
is that it &lt;em&gt;must&lt;/em&gt; be handled,
a guarantee that can&#39;t and won&#39;t be enforced within python.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;One of the key principles of python
is the ease of getting started.
Advanced parts of the language,
can be ignored until they are required.
Ignoring advanced parts of the language
is not just helpful for the learning process,
but also for slowly building up complexity
in the projects that we start.
When working with long running processes,
exception handling becomes an important part of the development process,
however, I don&#39;t know of a clear way
to find all the possible ways an exception could be raised
from a function we might use.
This makes the development process a continually iterative one,
run the code until you find an exception,
handle the exception and start again,
a process that is not ideal,
particularly for overnight calculations.&lt;/p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;/sup&gt;
&lt;p&gt;It was only using 32 bit unsigned integers, however that was decided to be more than anyone would need.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;/sup&gt;
&lt;p&gt;If you have to know, I was calculating the Diffusion constant,
a measure of how fast particles move over long timescales.
This is a linear function of the Mean Squared Displacement vs time.&lt;/p&gt;
&lt;/div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Failing Hard: How I &#39;lost&#39; two years of data</title>
		<published>2018-11-22T00:00:00+00:00</published>
		<updated>2018-11-22T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/failing-hard/"/>
		<id>https://malramsay.com/post/failing-hard/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/failing-hard/">&lt;p&gt;Failure is one of those topics
that is discussed far less than it should be
---It is hard to tell other people about mistakes you have made---
yet failure is usually a far better teacher than success.
This is why I want to share my story of
how I spent the first two years of my PhD collecting useless data
because of a bug in my code.&lt;/p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-problem&quot; aria-label=&quot;Anchor link for: the-problem&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I perform computer simulations of toy molecular systems,
using something simple and understandable
to gain insights into more complicated chemical systems.
This is just like in High School and Undergraduate Physics
where you perform calculations for objects in a frictionless vacuum.
Apart from the simplicity of a toy system,
a major benefit of using one is that you can
modify individual parameters of the system to test a hypothesis.
I had the hypothesis that rotational motion plays an important role
in the large scale translational motion---known as &lt;em&gt;diffusion&lt;/em&gt;,
so I was using a toy system which allowed me to control
the effective magnitudes of these two types of motion.
The small scale translational motion is related to the &lt;em&gt;mass&lt;/em&gt;;
heavy objects are more difficult to move in a straight line.
While the rotational motion is related to the &lt;em&gt;moment-of-inertia&lt;/em&gt;,
a measure of how difficult something is to rotate.
Though varying these two properties,
the &lt;em&gt;mass&lt;/em&gt; and the &lt;em&gt;moment-of-inertia&lt;/em&gt;,
I can establish how the translational and rotational motions
contribute to the rate of diffusion.
Setting the &lt;em&gt;moment-of-inertia&lt;/em&gt; really large
effectively stops the rotational motion,
so what happens to the diffusion?
Or the opposite, making the &lt;em&gt;moment-of-inertia&lt;/em&gt; really small
what is the resulting effect on the diffusion?&lt;/p&gt;
&lt;p&gt;Before I start changing the parameters of the toy model,
I need a reference (or control) model related to existing results.
Science is always performed with reference to our current understanding,
so the control model provides a point of comparison to existing results,
including other toy models and real chemical systems.
The molecule I am studying is comprised of three circles,
one large and two small, arranged like a Mickey Mouse head.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/molecules/trimer.png&quot; alt=&quot;The molecule I am studying, three circles arranged like a Mickey Mousehead.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To make calculations with these toy systems easier,
I use a system of units in which most quantities have a unitless value of 1.
Each particle has a point mass at the center of the circle of 1,
the large circle has a radius of 1,
a reasonable temperature is 1
---It is really nice that most of the multiplication and division disappears.
The smaller particles in the molecule have a radius of $0.63$
and are located on the circumference of the large particle
at an angle of $120^\circ$.&lt;/p&gt;
&lt;p&gt;I am using the above molecule in Molecular Dynamics (MD) simulations,
which use Newton&#39;s equations of motion to update the positions of each particle.
The translational acceleration $a$ of a molecule
is related to the force $F$ acting on it divided by the mass $m$
as given by the equation $a=F/m$.
While the angular acceleration $\alpha$
is related to the torque $\tau$ divided by the angular momentum $I$
as given by $\alpha = \tau/I$.
Molecular Dynamics simulations have two different methods
of dealing with the molecules I am using.
The first is to solve these equations for
each of a molecule&#39;s component particles individually,
followed by a second step which restores the molecular shape.
The second approach is
to calculate the force on each component particle,
then adding them together
to solve the above equations for the molecule as a whole.&lt;/p&gt;
&lt;p&gt;Molecular Dynamics simulations are a standard computational tool
for understanding a variety of problems
from crystal growth to protein folding.
As a widely used tool,
there are many different software packages available
for performing these simulations,
with each having their own strengths and weaknesses.
Using these software packages
involves expressing the simulation you want to perform
in the appropriate manner for the software.
At the start of my PhD
I decided that &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://hoomd-blue.readthedocs.io/en/stable/index.html&quot;&gt;Hoomd&lt;/a&gt; developed by the Glotzer group
at the University of Michigan was the most suitable
for the types of simulations I was going to be performing.
Soon after starting to understand the software,
there was a new major release,
which included a significant overhaul of the software.
This overhaul included changing how it handled the molecular calculations
from the first method to the second method,
something which I didn&#39;t realise at the time.
In getting my simulations to work with the new version of Hoomd,
I manually entered a moment of inertia,
calculated from each particle having a point mass of 1,
however the total mass of the molecule
was taken as the mass of an individual particle being 1 instead of 3.
By not actually checking the simulations were behaving as I intended
I spent the next two years of my PhD
characterising the behaviour of the wrong control experiment.&lt;/p&gt;
&lt;h2 id=&quot;finding-the-bug&quot;&gt;Finding the Bug&lt;a class=&quot;zola-anchor&quot; href=&quot;#finding-the-bug&quot; aria-label=&quot;Anchor link for: finding-the-bug&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I found the bug as part of trying to add additional functionality.
I was trying to randomly initialise velocities of particles in a configuration
such that they matched the desired temperature.&lt;/p&gt;
&lt;p&gt;The reason I found the bug was a result few the
Bug result of setting thermodynamic quantities
- temperature is a distribution of velocities
- energy is evenly distributed between translational and rotational motion&lt;/p&gt;
&lt;h2 id=&quot;lessons-learnt&quot;&gt;Lessons Learnt&lt;a class=&quot;zola-anchor&quot; href=&quot;#lessons-learnt&quot; aria-label=&quot;Anchor link for: lessons-learnt&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The lessons I have learnt from this experience
can be grouped into two groups,
the identification of bugs,
and the management of computational projects.&lt;/p&gt;
&lt;h3 id=&quot;identifying-bugs&quot;&gt;Identifying Bugs&lt;a class=&quot;zola-anchor&quot; href=&quot;#identifying-bugs&quot; aria-label=&quot;Anchor link for: identifying-bugs&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Even before the identification of this bug
I had developed a test suite for my code,
ensuring I was configuring the simulations in Hoomd
with the appropriate values.
What I wasn&#39;t checking,
was that these values produced the simulations I thought I was running.
Typically the method of doing this
is to take some previous results and replicating them,
although since no-one else has run simulations
on the particular system I am using it is a little difficult to do this.&lt;/p&gt;
&lt;p&gt;I had been told by my supervisor on many occasions
to confirm that the simulations were physically accurate,
usually with reference to conservation of energy.
This was really easy to ignore because that out of scope
for the code I was writing,
however, the idea of checking the simulation
is consistent with the laws of physics
was definitely worth considering earlier.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Testing is not about ensuring exact correctness,
it is also ensuring the absence of obvious errors.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A simple check of physicality is though the equi partition theorem,
which describes that each of the degrees of freedom,
whether translational or rotational,
have the same energy.
The relationships below link the mass $m$
and moment-of-inertia $I$
to the temperature $T$ providing a route to testing the simulations,
where the angled brackets $\langle \rangle$ denote an averaged quantity.&lt;/p&gt;
&lt;p&gt;\[
\begin{aligned}
\langle \frac{1}{2} m v^2 \rangle &amp;amp;= k_B T \\
\langle \frac{1}{2} I \omega^2 \rangle &amp;amp;= \frac{1}{2}k_B T
\end{aligned}
\]&lt;/p&gt;
&lt;p&gt;What might not be so obvious here is that the temperature $T$
in the above equations is not a static quantity,
instead fluctuating about the desired temperature,
a construct of the type of simulations I am performing.
This makes the task of testing the temperature is correct
significantly more difficult,
since it is not a set value,
lying in a distribution.
While creating automated testing for whether a value that varies can be difficult,
re-framing the test as &lt;em&gt;not-wrong&lt;/em&gt; is just as valid.
With this bug I created I was off by a factor of 3,
many times larger than any reasonable variation.
I didn&#39;t need to test that the temperature was exactly right,
I just needed to ensure it is not obviously wrong.&lt;/p&gt;
&lt;h3 id=&quot;rebuilding-the-dataset&quot;&gt;Rebuilding the Dataset&lt;a class=&quot;zola-anchor&quot; href=&quot;#rebuilding-the-dataset&quot; aria-label=&quot;Anchor link for: rebuilding-the-dataset&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;While the main storyline of this article has been
losing the years of my PhD research,
a major part of the learning experience
has been the rebuilding of all the data I had collected.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Reproducibility in science is not just for others to reproduce your work
it is also so you can reproduce it when you stuff up.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have been constantly working to improve my workflow,
as I learn more about what works and what doesn&#39;t
ending up with a structure based heavily on
the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/MolSSI/cookiecutter-cms&quot;&gt;cookiecutter-cms&lt;/a&gt; and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://drivendata.github.io/cookiecutter-data-science/&quot;&gt;cookiecutter-data-science&lt;/a&gt; project templates.
All the new work was structured into these project templates
which are well organised, with an emphasis on being able to replicate analyses.
What I really noticed in rebuilding my dataset
was that replicating these newer elements was simple and straightforward,
while the older projects just working out what I had done was a challenge,
let alone what needed to be recreated.&lt;/p&gt;
&lt;p&gt;Further to having a well organised data analysis,
having all the computational experiments,
including the permutations of every variable
and the scripts required to run the simulation was invaluable.
The experiments where this was up to date
were the simplest to deal with,
I just needed to run them again.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Although we often don&#39;t like to admit it, we all make mistakes.
Mistakes, failures, and bugs are all part of doing research.
While they may be disruptive and inconvenient,
they are only truly damaging when they keep re-occurring.
Making a mistake once is part of the process,
repeatedly making the same mistake is a problem.
Best practices like automated testing
are ways of making the mistakes that do occur obvious and simple to fix,
as well as providing a way of preventing them from reoccurring.
I am now fairly confident that should this particular bug reappear I will notice,
although that doesn&#39;t mean there isn&#39;t another hiding away for me to find.&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>A Complete Guide to Docker on Fedora</title>
		<published>2018-09-02T00:00:00+00:00</published>
		<updated>2018-09-02T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/docker-on-fedora/"/>
		<id>https://malramsay.com/post/docker-on-fedora/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/docker-on-fedora/">&lt;p&gt;While there are numerous guides to installing Docker on Fedora,
none of the guides leave the installation
in a state that I would consider usable.
This is intended to be a single complete guide
for the setup and configuration of Docker,
highlighting the differences that are required
to get Docker running on Fedora.
I will be demonstrating using Fedora 28,
however, this should be the same for previous or future releases.&lt;/p&gt;
&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;a class=&quot;zola-anchor&quot; href=&quot;#installation&quot; aria-label=&quot;Anchor link for: installation&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;This is the part that all the guides include,
including the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://developer.fedoraproject.org/tools/docker/docker-installation.html&quot;&gt;fedora documentation&lt;/a&gt;.
Docker is in the Fedora repositories
enabling installation using the &lt;code&gt;dnf&lt;/code&gt; package manager&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; dnf&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once installed, the Docker service can be started by running&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; systemctl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; start&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and should you want to start docker
every time you boot your machine
you can run&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; systemctl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; enable&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the above command doesn&#39;t start the Docker service immediately,
so you will have to run both the &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;enable&lt;/code&gt; commands
to have the Docker service running now and on following reboots.&lt;/p&gt;
&lt;p&gt;At this point you might want to try running a Docker container&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; hello-world&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;/usr/bin/docker-current:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Got&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; permission&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; denied&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; while&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; trying&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; to&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; connect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; the&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; daemon&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; socket&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; at&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; unix:///var/run/docker.sock&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;only you get a permission denied error.
It is possible to run Docker as &lt;code&gt;root&lt;/code&gt;,
however it is probably not the best idea
since it is kind of simple to make a mistake.&lt;/p&gt;
&lt;p&gt;If instead you received the message&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; hello-world&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;/usr/bin/docker-current:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Cannot&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; connect&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; to&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; the&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; daemon&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; at&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;unix:///var/run/docker.sock.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Is&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; the&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; daemon&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; running?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;this means you haven&#39;t started Docker and need to run&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; systemctl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; start&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;setting-permissions&quot;&gt;Setting Permissions&lt;a class=&quot;zola-anchor&quot; href=&quot;#setting-permissions&quot; aria-label=&quot;Anchor link for: setting-permissions&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;blockquote class=&quot;warning&quot;&gt;
&lt;p&gt;This section has the potential to break things which are difficult to fix.
Please be really careful, unlike me.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This follows the optional &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.docker.com/install/linux/linux-postinstall/&quot;&gt;post installation&lt;/a&gt; steps in the Docker documentation.&lt;/p&gt;
&lt;p&gt;For users to have permission to access the Docker socket,
they either need to be root,
or they can be a member of the &lt;code&gt;docker&lt;/code&gt; group.&lt;/p&gt;
&lt;p&gt;This group probably doesn&#39;t exist on your system yet,
though you can check by running&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;grep&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/group&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If there is no output the group does not yet exist
and can be created with the &lt;code&gt;groupadd&lt;/code&gt; command&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; groupadd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The group should now appear in the &lt;code&gt;/etc/group&lt;/code&gt; file&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; grep&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /etc/group&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;docker:x:1001:&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final step is adding yourself and/or any other users to the Docker group.
This is done with the command&lt;/p&gt;
&lt;blockquote class=&quot;warning&quot;&gt;
&lt;p&gt;Running the below command without append
will remove you from the &lt;code&gt;wheel&lt;/code&gt; group
meaning you will no longer be able to run commands with &lt;code&gt;sudo&lt;/code&gt;.
If you are the only user with root access
you will have to repair your install from a live image.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; usermod&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-append&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-groups&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;USER&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You have to go through the login process to update your group membership,
with the safest method being to open an &lt;code&gt;ssh&lt;/code&gt; connection to &lt;code&gt;localhost&lt;/code&gt;.
This means that if you accidentally remove yourself from the &lt;code&gt;wheel&lt;/code&gt; group,&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;
you just have to disconnect the session
to regain sudo permissions and fix things.&lt;/p&gt;
&lt;p&gt;To check everything is as expected,
the &lt;code&gt;groups&lt;/code&gt; command will list the groups you are a part of.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; groups&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;malcolm&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; wheel&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should have a list of groups similar to those output above.
Now you are a member of the &lt;code&gt;docker&lt;/code&gt; group
you can test Docker is working with the test image&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; hello-world&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Hello&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; from&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Docker!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;This&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; message&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; shows&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; that&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; your&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; installation&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; appears&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; to&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; be&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; working&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; correctly.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This indicates that Docker is working properly.&lt;/p&gt;
&lt;h2 id=&quot;mounting-local-directories&quot;&gt;Mounting Local Directories&lt;a class=&quot;zola-anchor&quot; href=&quot;#mounting-local-directories&quot; aria-label=&quot;Anchor link for: mounting-local-directories&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Now you have Docker working,
you probably want to do something useful with it
like access the local filesystem for processing.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-interactive&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-tty&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-volume&lt;/span&gt;&lt;span&gt; $(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;pwd&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;:/srv&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ubuntu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;root@aa7c5f9dcef9:/#&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; _&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above command creates an interactive terminal (tty)
running in an Ubuntu container,
with the prompt for the container now showing.
Additionally we have mounted the current directory to the container
at the &lt;code&gt;/srv&lt;/code&gt; folder of the container.
We can try and access the contents of the current directory
from within the container&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;root@aa7c5f9dcef9:/#&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ls:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; can&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;t open &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/srv&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;: Permission denied&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On an Ubuntu install this would work,
however Fedora uses SELinux for security,
which requires the appropriate labelling of file objects
for the processes using them.
By default Docker doesn&#39;t perform this labelling,
however we can tell it to with the &lt;code&gt;:z&lt;/code&gt; or &lt;code&gt;:Z&lt;/code&gt; suffixes for the volume.
The lowercase &lt;code&gt;:z&lt;/code&gt; allows multiple containers to access the volume
and the uppercase &lt;code&gt;:Z&lt;/code&gt; allows a single container to access the volume.
The command becomes&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;v&lt;/span&gt;&lt;span&gt; $(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;pwd&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;:/srv:Z&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ubuntu&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;root@aa7c5f9dcef9:/#&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ls&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /srv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;docker_on_fedora.md&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here I have used the more common shortened command line options,
&lt;code&gt;-it&lt;/code&gt; for the interactive terminal, and &lt;code&gt;-v&lt;/code&gt; for the volume.&lt;/p&gt;
&lt;p&gt;For a program that at first glance appears to simple to install,
Docker is rather difficult to get set up properly on Fedora.
Hopefully this&lt;/p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;/sup&gt;
&lt;p&gt;I have done this...twice&lt;/p&gt;
&lt;/div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Speed Through Specificity</title>
		<published>2018-08-07T00:00:00+00:00</published>
		<updated>2018-08-07T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/speed-through-specificity/"/>
		<id>https://malramsay.com/post/speed-through-specificity/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/speed-through-specificity/">&lt;p&gt;A common criticism about the Python programming language is that it is slow, often with reference to
a benchmark comparing a range of tasks. This criticism is widely addressed with articles by
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://jakevdp.github.io/blog/2014/05/09/why-python-is-slow/&quot;&gt;Jake van der Plass&lt;/a&gt; and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://hackernoon.com/why-is-python-so-slow-e5074b6fe55b&quot;&gt;Anthony Shaw&lt;/a&gt; being two excellent examples. While I don&#39;t disagree with any of
the points raised in these articles, I think they miss an important aspect of
performance---specificity. Python is a general purpose language, used for nearly everything from
embedded devices with &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://micropython.org/&quot;&gt;uPython&lt;/a&gt; to distributed processing of &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.youtube.com/watch?v=Hd_ydJeyr5M&quot;&gt;petabytes of data&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Speed comes through specificity for a task. Even when working with C or C++, which are generally
regarded as the reference standard for performance, there is still an argument to be made for
increased performance through hand optimised assembly. Writing assembly, which is the stream of
instructions the CPU interprets to do it&#39;s work, &lt;em&gt;can&lt;/em&gt; result in faster code than a compiler if you
know what you doing. Only nearly no-one actually writes assembly because we want our applications to
work on different processor architectures and to take advantage of the features of newer CPUs like
out-of-order execution.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; Instead of writing the fastest possible code for a particular processor
a more common approach is to provide hints to the compiler to help it optimise performance, trading
some performance for generality and simplicity.&lt;/p&gt;
&lt;p&gt;While it is uncommon to try and eke out every last bit of performance from a CPU, offloading the
work to more specialised hardware is commonplace. A CPU is a general purpose tool being adaptable to
many different types of operations which make it ideal for powering our computers, though to really
understand &lt;em&gt;fast&lt;/em&gt; we need specialised hardware like GPUs, FPGAs, or ASICs. Graphics Processing Units
(&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://en.wikipedia.org/wiki/Graphics_processing_unit&quot;&gt;GPUs&lt;/a&gt;)&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; are designed for performing the same operation on large quantities of data at the same
time; whether that is working out the colour of each pixel on a display, or the values at each point
of a large matrix. GPUs are designed at a hardware level to perform these types of tasks, eschewing
much of the capability of a CPU. Having hardware specific to a task is a key factor for performance
and Field Programmable Gate Arrays (&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://en.wikipedia.org/wiki/Field-programmable_gate_array&quot;&gt;FPGAs&lt;/a&gt;) are one method of achieving this. Instead of providing
a stream of instructions to be interpreted like a CPU or GPU, an FPGA is programmed by arranging the
circuits to perform the desired processing, providing phenomenal processing capability. FPGAs are
used in places like &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://people.eecs.berkeley.edu/~bora/publications/Asilomar06b.pdf&quot;&gt;signal processing&lt;/a&gt; and the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.eetimes.com/document.asp?doc_id=1262350&quot;&gt;Mars Rovers&lt;/a&gt;
as they allow for reprogramming for task switching or hardware updates. In some applications
the programmability of a FPGA is unnecessary, so Application-Specific Integrated Circuits (&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://en.wikipedia.org/wiki/Application-specific_integrated_circuit&quot;&gt;ASICs&lt;/a&gt;)
are used instead. An ASIC is a piece of silicon for processing a single specific task, with a common
use case being decoding video streams enabling you to watch hours of video in a single charge on
your phone. The progression of hardware from CPU to GPU to FPGA to ASIC represents an increasing
specificity to a task for improved performance at that task.&lt;/p&gt;
&lt;p&gt;The specificity of a task doesn&#39;t just refer to the low level details of processor architecture,
there are also the levels most developers are more likely to encounter of type and application
specificity. Python is a dynamically typed programing language, allowing variables to change type
and change functionality during execution. A byproduct of this dynamic typing is types and operator
functions need to be evaluated for each operation. Should we want to add the values of two lists
together like in the example below, each time the code reaches the line &lt;code&gt;result = i + j&lt;/code&gt; it has to
evaluate the types of both &lt;code&gt;i&lt;/code&gt; and &lt;code&gt;j&lt;/code&gt; to then work out how to perform the addition operation. Most
noticeable with large numbers of numerical values, the type evaluation takes far longer than the
addition operation.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;list1&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 3&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;list2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;4&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 6&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;list_result&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;for&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; j&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; zip&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;list1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; list2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    result&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; +&lt;/span&gt;&lt;span&gt; j&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    list_result&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;result&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A key concept in optimising numerical python code is limiting the number of type evaluations, with
the canonical method being to use &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://www.numpy.org/&quot;&gt;NumPy&lt;/a&gt; arrays. Instead of having a list which is able to hold
many different objects, NumPy arrays only contain a single type meaning the type only needs to be
evaluated once for the entire array. Other optimisation techniques for numerical python, namely
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://cython.org/&quot;&gt;Cython&lt;/a&gt; and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://numba.pydata.org/&quot;&gt;Numba&lt;/a&gt;, operate in a very similar way; reducing the calculation to a limited range of
types evaluated once. With these tools for numerical computation it is possible to get performance
on par or even exceeding that of C code.&lt;/p&gt;
&lt;p&gt;Even with the most specific type information the approach to solving a problem is a huge factor for
performance. Applications designed to solve a very specific problem are able to make significant
optimisations though handling only the cases the application will come across; an algorithm for
acyclic graphs doesn&#39;t have to handle cyclic graphs. Additionally an application for user
interaction can optimise for responsiveness, or a data processing application can optimise for data
throughput. The general purpose tool has to optimise for everything, so it is optimised for nothing.
Python is a general purpose programming language; not optimised for numerical computing, for the
web, for command line scripts, or any other use case. However, the adaptability of python has
allowed the development of application specific tools, like NumPy, containing a small range of very
specific functionality. Python also allows for the simple integration of problem specific hardware,
with tools like &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://documen.tician.de/pycuda/&quot;&gt;PyCUDA&lt;/a&gt;, &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://scikit-cuda.readthedocs.io/en/latest/&quot;&gt;scikit-cuda&lt;/a&gt;, &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://www.myhdl.org/&quot;&gt;MyHDL&lt;/a&gt;, &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://nifpga-python.readthedocs.io/en/latest/&quot;&gt;nifpga-python&lt;/a&gt;, and many others.&lt;/p&gt;
&lt;p&gt;Where speed is important, use the specific tool for the job. In most cases this isn&#39;t the python
standard library, instead it is usually just a &lt;code&gt;pip install&lt;/code&gt; away.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;/sup&gt;
&lt;p&gt;I guess you could also consider this a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://meltdownattack.com/&quot;&gt;bug...&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;/sup&gt;
&lt;p&gt;I would consider &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://en.wikipedia.org/wiki/Tensor_processing_unit&quot;&gt;Tensor Processing Units&lt;/a&gt; (TPUs) in the same category as GPUs, with the main difference being the targeted precision of mathematical operations.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;/sup&gt;
&lt;p&gt;Or &lt;code&gt;conda install&lt;/code&gt; for the tools requiring C/C++ libraries&lt;/p&gt;
&lt;/div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Compiling LaTeX on Travis-CI</title>
		<published>2018-07-16T00:00:00+00:00</published>
		<updated>2018-07-16T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/compiling-latex-on-travis/"/>
		<id>https://malramsay.com/post/compiling-latex-on-travis/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/compiling-latex-on-travis/">&lt;p&gt;One of the best parts of the current software development environment is the proliferation of
Continuous Integration (CI) services like &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://travis.org&quot;&gt;Travis-CI&lt;/a&gt;. These CI services plug into GitHub or other
code repositories to automatically run when new code is pushed to a repository. Typically CI is
used for running automated testing every time new code is added so you can be reasonably confident
a change hasn&#39;t broken any functionality. The premise of CI is the automation of tedious tasks like
running tests.&lt;/p&gt;
&lt;p&gt;When writing a LaTeX document, I find compilation the most tedious task. Particularly for large
documents, where it can take a long time. This means I run the compilation irregularly, invariably
resulting in a whole collection of errors that have accumulated and I now have to fix. Additionally
the compilation of LaTeX documents is often highly machine dependent, only working properly with a
specific configuration some reason. My final issue with LaTeX documents is ensuring all the files
required for compilation are included in the git repository, not just hiding in a directory
somewhere on your local filesystem.&lt;/p&gt;
&lt;p&gt;As a way of preventing these issues in the write-up of my PhD thesis, I have developed a
configuration for building LaTeX documents on Travis-CI. While I have come across other methods for
compiling LaTeX documents on Travis-CI, they all compromise on the workflow I would like. I want
a system that is;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;fast&lt;/em&gt;, with builds completing in a couple of minutes&lt;/li&gt;
&lt;li&gt;&lt;em&gt;adaptable&lt;/em&gt;, not having to manually specify every package I use&lt;/li&gt;
&lt;li&gt;&lt;em&gt;extensible&lt;/em&gt;, I can easily use &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://pandoc.org/&quot;&gt;pandoc&lt;/a&gt; to convert files to LaTeX before compilation&lt;/li&gt;
&lt;li&gt;uses &lt;code&gt;biber&lt;/code&gt;, the best practice for compiling the references&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;choosing-a-latex-distribution&quot;&gt;Choosing a LaTeX Distribution&lt;a class=&quot;zola-anchor&quot; href=&quot;#choosing-a-latex-distribution&quot; aria-label=&quot;Anchor link for: choosing-a-latex-distribution&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;There are a number of different methods to get a distribution of LaTeX installed. The main method
for Linux is the TeXLive distribution which is typically installed via the package manager. The
base TeXLive distribution is pre-installed on the Travis-CI image for A. However, this is only the
core extensions and so this approach is either &lt;em&gt;fast&lt;/em&gt; in that the image just boots up, or
&lt;em&gt;adaptable&lt;/em&gt; by downloading &lt;code&gt;texlive-full&lt;/code&gt; which takes a long time.&lt;/p&gt;
&lt;p&gt;The key issue with the TeXLive distribution is the lack of automatically downloading required
packages. A LaTeX distribution with this feature is &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://miktex.org/&quot;&gt;MiKTeX&lt;/a&gt;, having an installer that is only 200
MB compared to the ~3 GB of the complete TeXLive. MiKTeX also provides a
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://miktex.org/howto/miktex-docker&quot;&gt;docker container&lt;/a&gt; which is a great method for having exactly the same environment
compiling locally and through a CI service. Unfortunately MiKTeX doesn&#39;t install the &lt;code&gt;biber&lt;/code&gt; binary
on Linux (or macOS).&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; While it is possible to create a new container which includes the &lt;code&gt;biber&lt;/code&gt;
binary, extending and maintaining a Docker container is non-trivial making this approach less
favourable. That said, for other Docker-centric CI services this could be an excellent approach.&lt;/p&gt;
&lt;p&gt;Another newer and less well known LaTeX distribution is &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://tectonic-typesetting.github.io&quot;&gt;Tectonic&lt;/a&gt;. Although still considered beta
software, it works for most scenarios and has a lot of features that make it suitable for CI. I
would recommend installation with &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://tectonic-typesetting.github.io/en-US/install.html#the-anaconda-method&quot;&gt;conda&lt;/a&gt; although there are a range of
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://tectonic-typesetting.github.io/en-US/install.html&quot;&gt;installation methods&lt;/a&gt; for both Linux and macOS (currently no Windows support).
Like MiKTeX, Tectonic automatically downloads the packages required to compile a document, making
the same configuration adaptable to many different documents. Having conda as an installation method
is also particularly useful, allowing simple installation of many other tools (like &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://pandoc.org/&quot;&gt;pandoc&lt;/a&gt;) which
might be required to compile a more complex document. The only requirement not satisfied by Tectonic
is the automatic installation of &lt;code&gt;biber&lt;/code&gt;. Although not supported natively, it is &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/tectonic-typesetting/tectonic/issues/53&quot;&gt;possible to use
biber with tectonic&lt;/a&gt; as long as the appropriate binary for biber 2.5 is installed,
either from &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://sourceforge.net/projects/biblatex-biber/files/biblatex-biber/2.5/binaries/&quot;&gt;Sourceforge&lt;/a&gt; or using conda&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;conda&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; malramsay&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; biber==&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;2.5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;compiling-documents-with-tectonic&quot;&gt;Compiling Documents with Tectonic&lt;a class=&quot;zola-anchor&quot; href=&quot;#compiling-documents-with-tectonic&quot; aria-label=&quot;Anchor link for: compiling-documents-with-tectonic&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Typically, compiling documents with &lt;code&gt;tectonic&lt;/code&gt; requires a single command&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;tectonic&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; document.tex&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;creating the file &lt;code&gt;document.pdf&lt;/code&gt; and  automatically removing all intermediate files normally
associated with compiling LaTeX documents. To use &lt;code&gt;biber&lt;/code&gt; instead of &lt;code&gt;biblatex&lt;/code&gt; for the bibliography
the process is not quite so simple. In an attempt to make it easier I have created the Makefile
below which can be used to create a document.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;make&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Makefile&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; directory to put build files&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span&gt; :=&lt;/span&gt;&lt;span&gt; output&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;.PHONY&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; all&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;all&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; document.pdf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.pdf&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; %&lt;/span&gt;&lt;span&gt;.tex | &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	tectonic -o &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt; --keep-intermediates -r0 &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&amp;lt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	# Run biber if we find a .bcf file in the outpu&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	if [ -f &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;notdir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; $(&lt;/span&gt;&lt;span&gt;&amp;lt;:.tex=.bcf&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt; ]; then &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		biber --input-directory &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; $(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;notdir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; $(&lt;/span&gt;&lt;span&gt;&amp;lt;:.tex=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	f&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	tectonic -o &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt; --keep-intermediates &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&amp;lt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	cp &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;notdir&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; $@&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt; .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	mkdir -p &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$@&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This build process runs &lt;code&gt;tectonic&lt;/code&gt; once with the &lt;code&gt;--keep-intermediates&lt;/code&gt; option to generate the
intermediate files. I then check for the presence of a &lt;code&gt;.bcf&lt;/code&gt; file, which is the file &lt;code&gt;biber&lt;/code&gt; uses
to to it&#39;s thing. Tectonic is then run afterwards, which runs the compilation step as many times as
it needs to finalise the output. The final step is copying the output PDF from the build directory
to the local working directory.&lt;/p&gt;
&lt;p&gt;If you want to continue using your standard LaTeX build tool locally (in my case this is &lt;code&gt;latexmk&lt;/code&gt;),
you can check whether the &lt;code&gt;TRAVIS&lt;/code&gt; environment variable is defined enabling separate build
processes as in the example compilation step below.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;make&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.pdf&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; %&lt;/span&gt;&lt;span&gt;.tex | &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;ifdef&lt;/span&gt;&lt;span&gt; TRAVIS&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	tectonic -o &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt; --keep-intermediates -r0 &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&amp;lt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	# Run biber if we find a .bcf file in the outpu&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	if [ -f &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;notdir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; $(&lt;/span&gt;&lt;span&gt;&amp;lt;:.tex=.bcf&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt; ]; then &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;		biber --input-directory &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; $(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;notdir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; $(&lt;/span&gt;&lt;span&gt;&amp;lt;:.tex=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	f&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	tectonic -o &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt; --keep-intermediates &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&amp;lt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;else&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	latexmk -outdir=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt; -pdf -xelatex &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$&amp;lt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;endif&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	cp &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;notdir&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; $@&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt; .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;TRAVIS&lt;/code&gt; environment variable is defined on all Travis instances and can be set locally for
testing using the command&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;TRAVIS&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;u&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; make&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This sets the variable &lt;code&gt;TRAVIS&lt;/code&gt; just for the single command. Note that you will want to run a
&lt;code&gt;make clean&lt;/code&gt; between running with the different build systems as there will be incompatibility
between versions.&lt;/p&gt;
&lt;h2 id=&quot;configuring-travis-ci&quot;&gt;Configuring Travis CI&lt;a class=&quot;zola-anchor&quot; href=&quot;#configuring-travis-ci&quot; aria-label=&quot;Anchor link for: configuring-travis-ci&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;With a LaTeX distribution that is suitable for CI in &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://tectonic-typesetting.github.io&quot;&gt;Tectonic&lt;/a&gt;, how do I actually use Travis CI?&lt;/p&gt;
&lt;p&gt;This is a complicated process, which took me 81 attempts to get right. I hope this guide helps you
in making significantly fewer than me. The outline of the steps is below. The first three are
configuring and linking your accounts on GitHub and Travis. The rest of the steps are explained in
more detail in the rest of this document.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a public repository on GitHub. Travis CI only works with GitHub and while it does work
with private repositories they requires a paid account with Travis.&lt;/li&gt;
&lt;li&gt;Using your GitHub account, sign in to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/marketplace/travis-ci/plan/MDIyOk1hcmtldHBsYWNlTGlzdGluZ1BsYW43MA==#pricing-and-setup&quot;&gt;GitHub&lt;/a&gt; and add the Travis CI app to the
repository you want to activate. You&#39;ll need Admin permissions for that repository.&lt;/li&gt;
&lt;li&gt;Once signed in to Travis CI, go to your profile page and enable the repository you want to
build.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;.travis.yml&lt;/code&gt; file in the repository which tells Travis CI what to do. What you need
to put in the file is addressed &lt;a href=&quot;https://malramsay.com/post/compiling-latex-on-travis/#travis.yml&quot;&gt;below&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Commit the &lt;code&gt;.travis.yml&lt;/code&gt; file to the repository and push to GitHub. Travis will see the commit
and start the build process.&lt;/li&gt;
&lt;li&gt;Any further commits to the repository, whether to the master branch, other branches, tags, or
pull-requests will trigger a build on Travis.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;travis.yml&quot;&gt;Creating a .travis.ml file&lt;a class=&quot;zola-anchor&quot; href=&quot;#travis.yml&quot; aria-label=&quot;Anchor link for: travis.yml&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;.travis.yml&lt;/code&gt; file is comprised of a number of sections which I have described individually
below. The complete file is available for &lt;a href=&quot;/code/latex_travis/travis.yml&quot;&gt;download&lt;/a&gt;. To get Travis to start building
your repository, commit the &lt;code&gt;.travis.yml&lt;/code&gt; file to the repository and push the commit to GitHub.&lt;/p&gt;
&lt;p&gt;The first part of the &lt;code&gt;.travis.yml&lt;/code&gt; file is specifying the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.travis-ci.com/user/languages/&quot;&gt;language&lt;/a&gt;, which is
choosing which of the base containers to use for the compilation.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#3&quot;&gt;2&lt;/a&gt;&lt;/sup&gt; Since we are using conda for
installing any dependencies we can use the &lt;code&gt;minimal&lt;/code&gt; image.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;anguage&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;inimal&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next part is the cache, where I added the &lt;code&gt;$HOME/.cache/Tectonic&lt;/code&gt; directory. This is where
tectonic stores the downloaded tex packages. Rather than downloading all the packages required on
every build, the state of this entire directory will be downloaded at the start of the build and
updated when changed at the end of the build. This significantly speeds up the build process.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ache&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  d&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;irectories&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; $&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;HOME/.cache/Tectonic&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An additional directory that can be cached is &lt;code&gt;$HOME/miniconda&lt;/code&gt; so the conda packages are also
pre-installed on every build. This is less of a speed-up, although it can be helpful.&lt;/p&gt;
&lt;p&gt;The next step is specifying the steps to occur &lt;code&gt;before_install&lt;/code&gt;. Travis has a number of steps in the
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.travis-ci.com/user/customizing-the-build/#The-Build-Lifecycle&quot;&gt;lifecycle of a build&lt;/a&gt; providing ways of breaking the build into logical
steps. Each item in the list below is a bash command, which is run to update the environment of the
test container. This downloads and installs both &lt;code&gt;biber&lt;/code&gt; and conda, with conda then being used to
install tectonic. Any other dependencies that are required can also be added to this step, say if
you are converting Markdown to LaTeX with pandoc you could add &lt;code&gt;conda install pandoc&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;efore_install&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Download and install conda&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; w&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;get https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O $HOME/miniconda.sh&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; b&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ash $HOME/miniconda.sh -b -u -p $HOME/miniconda&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;xport PATH=&amp;quot;$HOME/miniconda/bin:$PATH&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ash -r&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Install tectonic&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;onda install -y -c conda-forge tectonic==0.1.8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;onda install -y -c malramsay biber==2.5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final step is the &lt;code&gt;script&lt;/code&gt;, the code that is used to determine success or failure. Like the
&lt;code&gt;before_install&lt;/code&gt; section, this is a list of commands which are executed one after another. Unlike the
&lt;code&gt;before_install&lt;/code&gt; section all these commands are run, even when a command fails.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;cript&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ake&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script section is also where you can add any other checks, like ensuring you haven&#39;t left in any
TODOs, or spelling mistakes. You can run any code you like and if the exit code is zero it is deemed successful,
while a non-zero exit code is a build failure. Note that a failing build will not go on to produce
a release.&lt;/p&gt;
&lt;h2 id=&quot;deploying-to-github-releases&quot;&gt;Deploying to GitHub Releases&lt;a class=&quot;zola-anchor&quot; href=&quot;#deploying-to-github-releases&quot; aria-label=&quot;Anchor link for: deploying-to-github-releases&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Having configured Travis to compile our document on every commit, it would be nice to actually do
something with the resulting document. Every repository on GitHub has releases, which can be
accessed by clicking on the releases link which is outlined in red on the image below.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/code/latex_travis/releases.jpg&quot; alt=&quot;The link to the releases page on GitHub&quot; /&gt;&lt;/p&gt;
&lt;p&gt;GitHub automatically creates a release for every tagged commit in the repository, creating a
downloadable &lt;code&gt;.zip&lt;/code&gt; and &lt;code&gt;.tar.gz&lt;/code&gt; of the state of the repository at that commit. It is also possible
to edit each of the releases, adding release notes or additional files like installers for a
variety of platforms. In this case we are going to use the GitHub releases to store the compiled
document for each tagged release providing a historical view of the document which is linked to the
code generating it.&lt;/p&gt;
&lt;p&gt;In writing of my PhD thesis it makes sense to tag releases using &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://semver.org/&quot;&gt;Semantic Versioning&lt;/a&gt;, or at least
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/malramsay64/phd-thesis/blob/master/planning/versioning.md&quot;&gt;a version of it&lt;/a&gt;. Other documents are much less linear, it might make sense to tag a
talk with the location it will be given, or the name of the conference. The requirements are
basically use numbers, letters and any of &lt;code&gt;._-+/&lt;/code&gt; --- see the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://git-scm.com/docs/git-check-ref-format&quot;&gt;git-check-ref-format&lt;/a&gt; documentation for more
specific details).&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;You can create a tag &lt;code&gt;my_tag&lt;/code&gt; for a release by running the command&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; tag&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; my_tag&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which will open an editor to write a message. A typical tag message is the repository name followed
by the tag, although that isn&#39;t the only approach. Like commit messages, you can specify
the message using the &lt;code&gt;-m&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;By default, git doesn&#39;t push tags to a remote, requiring the
&lt;code&gt;--tags&lt;/code&gt; option&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;git&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; push&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-tags&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before pushing your newly created tag, you are going to want to configure Travis to upload releases
to GitHub. The best method for this is to use the Travis command line client, which can be installed
by following &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/travis-ci/travis.rb#installation&quot;&gt;these instructions&lt;/a&gt;. Once installed you can run the command&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;travis&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; setup&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; releases&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which will prompt for your GitHub credentials and other information about the release. These are
used to generate a personal access token for GitHub which Travis uses to authenticate when uploading
the release. The &lt;code&gt;travis&lt;/code&gt; client will encrypt the token, and update your &lt;code&gt;.travis.yml&lt;/code&gt; with a deploy
section which looks something like this;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;eploy&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  p&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;rovider&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;eleases&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  a&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;pi_key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ecure&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; your encrypted token will be here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  f&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ile&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;hesis.pdf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;kip_cleanup&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since we only want to deploy on tagged commits, we can use the
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.travis-ci.com/user/deployment#Conditional-Releases-with-on%3A&quot;&gt;conditional deployment&lt;/a&gt; options to
conditionally deploy. This gives the following deploy section.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;eploy&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  p&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;rovider&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; r&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;eleases&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  a&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;pi_key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ecure&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; your encrypted token will be here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  f&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ile&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;hesis.pdf&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;kip_cleanup&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;  on&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    t&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ags&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I have made the entire &lt;code&gt;.travis.yml&lt;/code&gt; file available for &lt;a href=&quot;/code/latex_travis/travis.yml&quot;&gt;download&lt;/a&gt; should you want to
get started quickly. Or alternatively have a look at my repositories &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/malramsay64/usyd-beamer-theme&quot;&gt;usyd-beamer-theme&lt;/a&gt; or
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/malramsay64/phd-thesis&quot;&gt;phd-thesis&lt;/a&gt; which I have set up to use this workflow. This file along with the &lt;a href=&quot;/code/latex_travis/Makefile&quot;&gt;Makefile&lt;/a&gt; this
should enable this process to work for the compilation of most documents.&lt;/p&gt;
&lt;p&gt;While the process is complicated to set up, once completed it shouldn&#39;t require much effort to
maintain. There is the rationale it might save you some time, however I think it is cool which is
all justification I needed.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Update 2018-07-26:&lt;/strong&gt; Since initially writing this post I have created a conda package to
distribute the biber binary. I have updated the post and included files to use this package since it
significantly simplifies the installation. The steps from the original &lt;code&gt;travis.yml&lt;/code&gt; file to manually
install biber are below.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Download and install biber installing executable as biber2.5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; w&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;get https://sourceforge.net/projects/biblatex-biber/files/biblatex-biber/2.5/binaries/Linux/biber-linux_x86_64.tar.gz -O $HOME/downloads/biber.tar.gz&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ar xvzf $HOME/downloads/biber.tar.gz -C $HOME/bin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;v $HOME/bin/biber $HOME/bin/biber2.5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;xport PATH=&amp;quot;$HOME/bin:$PATH&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;/sup&gt;
&lt;p&gt;I should note that MiKTeX will install &lt;code&gt;biber&lt;/code&gt; on a Windows system. So if you wanted to set
up a Windows CI config I guess MiKTeX is a great approach.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;/sup&gt;
&lt;p&gt;There are more special characters supported, I just listed the most common ones. See the
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://git-scm.com/docs/git-check-ref-format&quot;&gt;docs&lt;/a&gt; for more specific information.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;/sup&gt;
&lt;p&gt;You may notice that the minimal is not listed in the documentation. There is a
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/travis-ci/docs-travis-ci-com/issues/910#issuecomment-356915625&quot;&gt;GitHub issues&lt;/a&gt; to
rectify this in which there is documentation.&lt;/p&gt;
&lt;/div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Experi: A tool for computational experiments</title>
		<published>2018-06-25T00:00:00+00:00</published>
		<updated>2018-06-25T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/experi-a-tool-for-computational-experiments/"/>
		<id>https://malramsay.com/post/experi-a-tool-for-computational-experiments/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/experi-a-tool-for-computational-experiments/">&lt;h2 id=&quot;abstract&quot;&gt;Abstract&lt;a class=&quot;zola-anchor&quot; href=&quot;#abstract&quot; aria-label=&quot;Anchor link for: abstract&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;One of the key features of computational experiments is being able to run the experiment over
a large variable space. However, in my experience there aren&#39;t tools available to assist with this,
particularly in the realm of High Performance Computing (HPC), where bash arrays and loops are
commonplace. Using the current toolset, I made lots of errors in the specification of files,
turning a &#39;quick edit&#39; into a tedious process of find the bug. To make complicated experiment
variable expression a simple and intuitive task I have developed &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/malramsay64/experi&quot;&gt;Experi&lt;/a&gt;, which takes a list of
commands and variables from a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://en.wikipedia.org/wiki/YAML&quot;&gt;YAML&lt;/a&gt; input file and either runs them for testing or creates and
submits files to an HPC scheduler. I have designed Experi to be a simple replacement for current
workflows, using shell commands into which you add variables, and variables defined along with how
they are combined. I have been using Experi as I have been developing it and though it hasn&#39;t
prevented me from running software with bugs, I can specify the variables I want to use for
a simulation without having to worry about the execution. As an added benefit, I also have
a complete record of every simulation that I run which is version controllable.&lt;/p&gt;
&lt;p&gt;Experi is installable using both &lt;code&gt;pip&lt;/code&gt; and &lt;code&gt;conda&lt;/code&gt;,&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; pip&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; experi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Currently, Experi only runs on python 3.6, because that is what I use and &quot;premature optimisation is
the root of all evil.&quot; If you would like to use Experi and can&#39;t use python 3.6, please let me know
along with which python version you do use and I can look into supporting it.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I work in science where most compute intensive problems are handled by High Performance Computing
(HPC) clusters. From the user perspective the entire cluster appears as a single host, with
resources accessible through a job scheduler. You submit work as a script to the scheduler along
with the resources required, where it will wait in a queue for those resources to be available. Once
your script is running, it can access the resources allocated. When using a parallelism technology
like MPI, the MPI software will detect all the resources and connecting the appropriate pieces
together. This makes for a reasonably simple method of accessing the vast compute capabilities of
HPCs.&lt;/p&gt;
&lt;p&gt;Bash scripts are normally used to specify the work to take place; with additional scheduler
directives denoting the resources required. One of scheduler directives you can specify is to make
the job as an array job, where multiple instances of your file are submitted at once with an array
index. In some ways this is similar to a numpy array operation rather than a &lt;code&gt;for&lt;/code&gt; loop. The array
job is able to make best use of the scheduler, splitting a large task into many smaller tasks
which are easier to fit onto the cluster. This kind of workflow is ideal for running an experiment
at different conditions, or with many replications.&lt;/p&gt;
&lt;p&gt;With the scheduler I use, &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://www.pbspro.org/&quot;&gt;PBSPro&lt;/a&gt; an array job is specified using the &lt;code&gt;-J&lt;/code&gt; flag and takes an
argument instructing how to create the values. The argument has the form
&lt;code&gt;&amp;lt;start_index&amp;gt;-&amp;lt;end_index&amp;gt;:&amp;lt;increment&amp;gt;&lt;/code&gt;, e.g. &lt;code&gt;1-10:2&lt;/code&gt; will generate five jobs for the scheduler
which will each have the bash variable &lt;code&gt;PBS_ARRAY_INDEX&lt;/code&gt; containing one of &lt;code&gt;1,3,5,7,9&lt;/code&gt;. While this
can be useful where you need to vary a parameter over a sequence of integers, the more likely use
case is creating a list of values which you are then indexing using &lt;code&gt;PBS_ARRAY_INDEX&lt;/code&gt; like the
example below&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ARRAY&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; =&lt;/span&gt;&lt;span&gt; (1.0&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1.5&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2.0&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2.5&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 3.0&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 4.0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;{&lt;/span&gt;&lt;span&gt;ARRAY&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;[&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;PBS_ARRAY_INDEX&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When specifying my jobs for the scheduler I was making lots of mistakes writing a job script that
was valid and reflected my intentions. This was difficult in that each job involved changing
multiple variables and I only had a single index. While Bash is excellent for specifying complex
commands for the computer to interpret, when dealing with long commands and lots of variables it
quickly becomes too complicated to easily read. When I am designing a computational experiment
I think about the commands I want to run separately from the variables I am going to use in those
commands.&lt;/p&gt;
&lt;p&gt;My experiments have a recipe that look something like;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a state with the properties I desire. This could be a liquid, a crystal, or a combination
of the two.&lt;/li&gt;
&lt;li&gt;Bring the initial state to the conditions I want to collect data, ensuring the new state is
representative of those conditions.&lt;/li&gt;
&lt;li&gt;Collect lots of data on how my experiment behaves at this specific condition.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I have presented each of these steps abstractly as they are applicable to many different
experiments. It is also the level of abstraction for the command line interface, &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/malramsay64/statdyn-simulation&quot;&gt;sdrun&lt;/a&gt;, I have
written to help run my experiments. A command I would run for the create step would be&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; sdrun&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-temperature&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0.1&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-num-steps&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 100&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-space-group&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p2&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; create&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; configuration.out&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I specify the variables for the simulation, the type of simulation being &lt;code&gt;create&lt;/code&gt;, and then the
output file.&lt;/p&gt;
&lt;p&gt;For each of the three steps above I now want to define the specific variables I am using;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Crystal Structure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;p2&lt;/li&gt;
&lt;li&gt;p2gg&lt;/li&gt;
&lt;li&gt;pg&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Temperature:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0.4&lt;/li&gt;
&lt;li&gt;0.45&lt;/li&gt;
&lt;li&gt;0.50&lt;/li&gt;
&lt;li&gt;0.6&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1_000_000&lt;/li&gt;
&lt;li&gt;1_000_000&lt;/li&gt;
&lt;li&gt;500_000&lt;/li&gt;
&lt;li&gt;100_000&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I want to run the same simulation for each of three different crystal
structures, p2, p2gg, and pg. This simulation will be over a range of
temperatures, with smaller spacing at lower temperatures where there is
interesting behaviour. Also the effects I am looking for happen faster at
higher temperatures so I am going to run shorter simulations for the higher
temperatures.&lt;/p&gt;
&lt;p&gt;In presenting the values for the variables in a concise way, a table is possibly clearer though much
less concise, I am nearly using &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://en.wikipedia.org/wiki/YAML&quot;&gt;YAML&lt;/a&gt; syntax. YAML is a method of encoding both structure and
data in a human readable format. You can think about it as a way of defining python lists and dicts
in a text file. Lists are a series of bullet points,&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;with the snippet above being the list &lt;code&gt;[0, 1, 2, 3, 4]&lt;/code&gt; in python. Dicts are
mappings of values using the colon &lt;code&gt;:&lt;/code&gt; as a separator&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ello&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; w&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;orld&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;oo&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; b&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;being the dict &lt;code&gt;{&quot;hello&quot;: &quot;world&quot;, &quot;foo&quot;: &quot;bar&quot;}&lt;/code&gt; in python.&lt;/p&gt;
&lt;p&gt;With the goal of being able to easily specify commands required for an experiment in addition to the
variables to fill into those commands, I have developed &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/malramsay64/experi&quot;&gt;Experi&lt;/a&gt;. It takes a YAML file as input,
declaring the experiment that will take place, and either creates and submits files to the scheduler
or runs the experiment in the current shell for testing. Taking the example I constructed above, to
completely specify the experiment for Experi;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; experiment.yml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ommands&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;drun --space-group {crystal} -t 0.3 -s 1000 create configuration-{crystal}-0.3.out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    sdrun&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    --space-group {crystal}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    --init-temp 0.3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    --temperature {temperature}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    --num-steps {steps}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    equil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    config-{crystal}-0.3.out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    config-{crystal}-{temperature:.2f}.out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;drun --space-group {crystal} -t {temperature} -s {steps} prod config-{crystal}-{temperature:.2f}.out&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ariables&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  c&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;rystal&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;g&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;2gg&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  z&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ip&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    t&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;emperature&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0.4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0.45&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0.50&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0.6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;teps&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1_000_000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1_000_000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 500_000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 100_000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;bs&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  n&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;cpus&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 12&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  w&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;alltime&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 100:00:00&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  j&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;e&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are three commands which will be run; firstly a &lt;code&gt;create&lt;/code&gt; step, secondly an &lt;code&gt;equil&lt;/code&gt;
step, finally a &lt;code&gt;prod&lt;/code&gt; step. The longer command of the &lt;code&gt;equil&lt;/code&gt; step is broken
over multiple lines, and since I am using the YAML syntax for a multiline string &lt;code&gt;&amp;gt;&lt;/code&gt;, it is
interpreted as a single long string hence no need for line continuation characters. Where I want to
replace the variables I have enclosed the variable name in braces (&lt;code&gt;{}&lt;/code&gt;), which will be
automatically replaced using &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://pyformat.info/&quot;&gt;python string formatting&lt;/a&gt;. Using this format allows for modifiers
like &lt;code&gt;{temperature:.2f}&lt;/code&gt; which is replaced with the temperature as a float having two decimal
places, i.e. &lt;code&gt;0.4&lt;/code&gt; -&amp;gt; &lt;code&gt;&#39;0.40&#39;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Commands in Experi are guaranteed to be run in a linear fashion, i.e. none of the &lt;code&gt;equil&lt;/code&gt;
simulations will run until the &lt;code&gt;create&lt;/code&gt; simulations are finished, with the additional constraint
that all previous steps are successful. This holds for both running in the local terminal, or using
the job scheduler.&lt;/p&gt;
&lt;p&gt;Variables for the experiment are specified in their own section, with the variable name as the
dictionary key, followed by a list of values---a single value is also supported. Variables can take
on any valid string with the exception of &lt;code&gt;zip&lt;/code&gt; or &lt;code&gt;product&lt;/code&gt; which are used define how the variables
are combined. &lt;code&gt;zip&lt;/code&gt; acts on the variables like a zipper, taking variables with the same number of
values and matching the values up 1:1. In this case we get&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;temperature: 0.4 steps: 1_000_000&lt;/li&gt;
&lt;li&gt;temperature: 0.45 steps: 1_000_000&lt;/li&gt;
&lt;li&gt;temperature: 0.50 steps: 500_000&lt;/li&gt;
&lt;li&gt;temperature: 0.6 steps: 100_000&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;product&lt;/code&gt; is also somewhat self-descriptive, taking the &#39;product&#39; of all the values to give all
possible combinations. &lt;code&gt;product&lt;/code&gt; is the default operation and is not explicitly required so each of
the three crystals will have simulations with the temperature and steps listed above.&lt;/p&gt;
&lt;p&gt;Where multiple &lt;code&gt;zip&lt;/code&gt; operations are required, a list of dictionaries can be supplied to the &lt;code&gt;zip&lt;/code&gt;
key with each being zipped separately before taking the product of each item in the list.&lt;/p&gt;
&lt;p&gt;The final part of the YAML file is the specification of the options for the PBS scheduler. There are
some values which can be passed as keys;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;select/nodes&lt;/li&gt;
&lt;li&gt;ncpus/cpus&lt;/li&gt;
&lt;li&gt;ngpus/gpus&lt;/li&gt;
&lt;li&gt;memory/mem&lt;/li&gt;
&lt;li&gt;walltime&lt;/li&gt;
&lt;li&gt;cputime&lt;/li&gt;
&lt;li&gt;project&lt;/li&gt;
&lt;li&gt;setup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While any options not specifically encoded can be passed by using the flag as the key. In the
example above I have used &lt;code&gt;j: oe&lt;/code&gt; which will become the scheduler option &lt;code&gt;-j oe&lt;/code&gt; which is specifying
that the stdout (&lt;code&gt;o&lt;/code&gt;) and stderr (&lt;code&gt;e&lt;/code&gt;) will be joined in the stdout stream.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;setup&lt;/code&gt; key allows for commands to load the required variables, modules, or anything else that
is required before each command is run.&lt;/p&gt;
&lt;p&gt;It should be noted that YAML is not without &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://arp242.net/weblog/yaml_probably_not_so_great_after_all.html&quot;&gt;idiosyncrasies&lt;/a&gt;. However, it is
powerful and widespread and I like and understand the format. The structure of Experi is not
specific to YAML and could easily work with JSON or TOML input files if someone wants to put effort
into the implementation and documentation.&lt;/p&gt;
&lt;p&gt;With the input file specified, assuming it is named &lt;code&gt;experiment.yml&lt;/code&gt;, the simulation can be
submitted to the scheduler using the command&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; experi&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which will submit the job to the scheduler if it is available. Regardless of the presence of the
scheduler, a file is created for each command which includes all the combinations of variables
specified in a bash array which is suitable for manual submission.&lt;/p&gt;
&lt;p&gt;Using Experi for the design and execution of computational experiments has significantly benefited
my science workflow. This is most noticeable with the replication and modification of experiments.
I have a complete record of all the commands and options required for the experiment to run and
I can focus on modifying values without having to worry about how to loop over the range of
variables I require.&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Distributing a Hoomd Plugin</title>
		<published>2018-06-03T00:00:00+00:00</published>
		<updated>2018-06-03T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/distributing-a-hoomd-plugin/"/>
		<id>https://malramsay.com/post/distributing-a-hoomd-plugin/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/distributing-a-hoomd-plugin/">&lt;p&gt;A piece of software I have been using in my research is &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://hoomd-blue.readthedocs.io/en/stable/&quot;&gt;Hoomd&lt;/a&gt;,
a &#39;relatively&#39; new package for running Molecular Dynamics (MD) simulations.
These MD simulations have the basic premise of
throwing hundreds of balls into a box and shaking it
to find out what happens.
The relative newness of Hoomd
is in comparison to other software packages like &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://lammps.sandia.gov/&quot;&gt;LAMMPS&lt;/a&gt; and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://www.gromacs.org/&quot;&gt;GROMACS&lt;/a&gt;
which have been around for decades,
while the initial release of Hoomd was in 2012.
There are some major benefits of a newer approach to MD simulations
and Hoomd is most notably designed to leverage the computational power of GPUs.
Despite the benefits of a modern approach,
Hoomd doesn&#39;t have the range of built-in simulation types
of the more mature software packages.
To allow researchers with specific problems to still use Hoomd,
it has a plugin architecture
to simplify the implementation of custom functionality.&lt;/p&gt;
&lt;p&gt;This article documents how I took a plugin I wrote for my research
and made it installable in a conda environment alongside Hoomd.&lt;/p&gt;
&lt;h2 id=&quot;the-plugin&quot;&gt;The Plugin&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-plugin&quot; aria-label=&quot;Anchor link for: the-plugin&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The example plugin is one which I have written for my own research.
It implements a harmonic pinning potential on the
positions and rotations of rigid bodies
in a MD simulation.
The complete plugin is available for reference on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/malramsay64/hoomd-harmonic-force&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To get started writing your own plugin
there is a short guide in the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://hoomd-blue.readthedocs.io/en/stable/developer.html&quot;&gt;documentation&lt;/a&gt;
which directs you to the &lt;code&gt;example_plugin&lt;/code&gt; in the Hoomd source code.
This code is located on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://bitbucket.org/glotzer/hoomd-blue/src/maint/&quot;&gt;bitbucket&lt;/a&gt; and there is also a copy on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/joaander/hoomd-blue&quot;&gt;GitHub&lt;/a&gt;.
I also found that finding a class that shared the same base class was useful for writing the implementation.&lt;/p&gt;
&lt;h2 id=&quot;the-makefile&quot;&gt;The Makefile&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-makefile&quot; aria-label=&quot;Anchor link for: the-makefile&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The example code for the &lt;code&gt;example_plugin&lt;/code&gt; doesn&#39;t include a &lt;code&gt;Makefile&lt;/code&gt;,
requiring multiple steps to compile the plugin.
A Makefile is useful in this case for
both being able to install with a single command
and ensuring the install options are consistent with the conda installed Hoomd.&lt;/p&gt;
&lt;p&gt;While my plugin doesn&#39;t use CUDA for calculations,
for compatibility with the conda installed Hoomd
enabling CUDA on Linux is required.
When I was compiling with CUDA disabled I came across unusual behaviour,
for example numbers being far too large, which appeared to be a buffer overflow.
I believe this is because of conditional definitions in the header files
resulting in a slightly different memory layout
for a class defined with or without CUDA.&lt;/p&gt;
&lt;p&gt;If you are able to build your plugin using &lt;code&gt;cmake&lt;/code&gt; like the example plugin,
the following Makefile will make compilation simpler
and set the appropriate flags assuming Hoomd was installed using conda.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;make&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; build&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; Check OS to determine if CUDA is enabled, defaults to True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;CUDA_ENABLED&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;UNAME_S&lt;/span&gt;&lt;span&gt; :=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; $(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;shell&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; uname -s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;ifeq&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;UNAME_S&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;Linux)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	CUDA_ENABLED=Tru&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;endif&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;ifeq&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;UNAME_S&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;Darwin)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	CUDA_ENABLED=Fals&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;endif&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;all&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; $(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	cd &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt;; cmake .. -DENABLE_CUDA=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;CUDA_ENABLED&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;	$(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;MAKE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt; -C &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;install&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; all&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;	$(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;MAKE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt; -C &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt; instal&lt;/span&gt;&lt;span&gt;l&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;clean&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	rm -rf &lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;test&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	pytes&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;$(&lt;/span&gt;&lt;span&gt;build_dir&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	mkdir -p &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;$@&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;.PHONY&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; test clean&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apart from making it simpler to install the package manually,
it also makes it simplifies the build using conda.&lt;/p&gt;
&lt;h2 id=&quot;packaging&quot;&gt;Packaging&lt;a class=&quot;zola-anchor&quot; href=&quot;#packaging&quot; aria-label=&quot;Anchor link for: packaging&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The conda package manager is the recommended method of installing Hoomd,
primarily because of the simplicity of installation.
Conda allows you to upload your own packages for anyone to download,
which is how we are going to make this plugin simple to install.&lt;/p&gt;
&lt;p&gt;To define the package for upload to the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://anaconda.org/&quot;&gt;Anaconda Cloud&lt;/a&gt; repository
we have to write a &lt;code&gt;meta.yaml&lt;/code&gt; file.
This defines everything required to build the package including;
the package details,
where to find the source code,
dependencies required to build the package,
dependencies to install the package, and
how to build the package.
There is [extensive documentation][meta.yaml documentation] for the &lt;code&gt;meta.yaml&lt;/code&gt; file
which covers a variety of use cases.
The file uses the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://yaml.org/&quot;&gt;yaml&lt;/a&gt; syntax,
which is a method of encoding python data structures
in a more human readable format.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;meta.yaml&lt;/code&gt; file contains a series of sections
each containing different information about the package.
Some of these keys are;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;package&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;about&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;source&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dependencies&lt;/code&gt;, and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;test&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The documentation link above contains more extensive information on the options available for each
of these sections. I am going to explain how I have used all the different sections to create this
plugin.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;package&lt;/code&gt; key contains the name and version of the package. This section is
compulsory, while all others are optional, although that doesn&#39;t mean they
aren&#39;t required.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ackage&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    n&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ame&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;oomd-harmonic-force&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    v&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ersion&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0.1.7&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;about&lt;/code&gt; key is where you can find more information on the package, including the
homepage and the license the package uses. In this case the homepage is a link to the Github
repository.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;bout&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  h&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ome&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ttps://github.com/malramsay64/hoomd-harmonic-force&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  l&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;icense&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; M&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;IT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  l&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;icense_file&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; L&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ICENSE&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;source&lt;/code&gt; section defines where to find the source code for the build phase. I have specified
a specific tag from the git repository, enabling me to checkout an old commit to build an older
version.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ource&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  g&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;it_url&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ttps://github.com/malramsay64/hoomd-harmonic-force.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  g&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;it_rev&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; v&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;0.1.7&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another useful option for the &lt;code&gt;source&lt;/code&gt; section is use the &lt;code&gt;path&lt;/code&gt; key. This key
allows for the specification of the relative (or absolute) path to the source
code. This is most useful in the development process, allowing for quickly
testing whether a change has worked.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ource&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;    p&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ath&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; .&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next section is the &lt;code&gt;requirements&lt;/code&gt;, defining both the packages required for
the build phase, and those required when the package is installed. These package definitions
include the specification of compatible versions of the dependencies. The version specification
is flexible enough for complex build processes, while also having the option for simplicity.
In this guide I describe the simplest method of version specification. When you need something more complex,
the documentation on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://conda.io/docs/user-guide/tasks/build-packages/variants.html&quot;&gt;build variants&lt;/a&gt; covers a wide range of use cases.&lt;/p&gt;
&lt;p&gt;The build requirements are all the programs required for the build; in this
case compilation with &lt;code&gt;cmake&lt;/code&gt;. Since we are building a python module we require
both python and setuptools. I am using python 3.6, so have set the python
version to &lt;code&gt;3.6.*&lt;/code&gt; which means any point release of python 3.6, at the time of
writing being 3.6.5. The numpy and Hoomd dependencies are also requirements of
the build process, for which I have specified the latest versions. For python,
numpy and Hoomd you can specify the versions you use for your work. Where you
use multiple versions, like python 2.7, 3.5, and 3.6, the start of the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://conda.io/docs/user-guide/tasks/build-packages/variants.html&quot;&gt;build
variants documentation&lt;/a&gt; has examples to adapt. The next
dependency, cudatoolkit, is not strictly necessary in this case, however before
Hoomd v2.2.5 there was a typo in the Hoomd version specification allowing
a newer incompatible version to be installed. The final requirement, &lt;code&gt;cmake&lt;/code&gt;,
builds the package and while you might have it installed on your system
already, someone else may not. It should be noted that the &lt;code&gt;nvcc&lt;/code&gt; binary is
also required on linux which I can&#39;t find a conda package for.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;equirements&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  b&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;uild&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ython 3.6.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;etuptools&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;umpy 1.14.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;oomd 2.3.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;udatoolkit 8.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;make &amp;gt;=2.8.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the build dependencies specified, we need to specify the run dependencies.
Version numbers under the [semantic versioning][] scheme have a format of
&lt;code&gt;&amp;lt;major&amp;gt;.&amp;lt;minor&amp;gt;.&amp;lt;patch&amp;gt;&lt;/code&gt;. Version numbers with the same major and minor
numbers are typically compatible which is why I have specified the run
requirements with the same minor version as the build requirements. I have come
across previous versions of Hoomd which are only compatible with a singe patch
version of python. Unfortunately, you can&#39;t pin the python version to a single patch
as conda build &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/conda/conda-build/issues/2571&quot;&gt;overrides the pinning&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;equirements&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  r&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;un&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ython 3.6.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;umpy 1.14.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;oomd 2.3.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;udatoolkit 8.*&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Having waded through the complications of dependency specification we now need
to tell conda how to build our plugin. This is where creating the Makefile is
useful, we can now use the same commands for build process as for a manual
installation; &lt;code&gt;make clean &amp;amp;&amp;amp; make install&lt;/code&gt;. The &lt;code&gt;make clean&lt;/code&gt; command removes
the build files when using the &lt;code&gt;path&lt;/code&gt; option for the &lt;code&gt;source&lt;/code&gt;. The other
element of the build section is the number, incremented when uploading another
package with the same version and reset to 0 on a new version. The number acts
as a sub-version number allowing for small fixes, like correcting the version
pinning, without incrementing the package version number.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;uild&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;cript&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ake clean &amp;amp;&amp;amp; make install&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  n&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;umber&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final section I will discuss is the test section. At its simplest this can
import your newly created package, or it can run a complex unit and integration
test suite. The test section failing will be a fail the build, providing
a final check for bugs before release. In the example below I am checking the
package will import as a first simple test, before running more extensive
testing with &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.pytest.org/en/latest/&quot;&gt;pytest&lt;/a&gt;. To ensure pytest is installed, there is
a &lt;code&gt;requirements&lt;/code&gt; key in the &lt;code&gt;test&lt;/code&gt; section allowing for the specification
of test specific requirements. I have also specified the files for running the
tests using the &lt;code&gt;source_files&lt;/code&gt; key, which is the entire directory of test
files. The final key is the &lt;code&gt;commands&lt;/code&gt; to run your test suite. Since I have the
test rule configured in my Makefile I make use of it here.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;est&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  i&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;mports&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;oomd.harmonic_force&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  r&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;equires&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ytest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ource_files&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;est/*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  c&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ommands&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ake test&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I have put all the snippets into a single block of code at the end of this article or downloadable
&lt;a href=&quot;/code/hoomd-plugin/meta.yaml&quot;&gt;here&lt;/a&gt;. For more complex examples, you can have a look at the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://bitbucket.org/glotzer/hoomd-blue/src/maint/conda-recipe/&quot;&gt;Hoomd
repository&lt;/a&gt; or the example in &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/malramsay64/hoomd-harmonic-force/blob/master/meta.yaml&quot;&gt;my repository&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;building-and-distribution&quot;&gt;Building and Distribution&lt;a class=&quot;zola-anchor&quot; href=&quot;#building-and-distribution&quot; aria-label=&quot;Anchor link for: building-and-distribution&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;With all the metadata defined, conda makes it straightforward to create the
package for upload. There are two conda packages required for building and
uploading to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://anaconda.org/&quot;&gt;Anaconda Cloud&lt;/a&gt;, which are both required in the root
environment. You can install both packages with the command below, where the
&lt;code&gt;-n&lt;/code&gt; flag specifies installing to the &lt;code&gt;root&lt;/code&gt; environment.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; conda&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; root&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; conda-build&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; anaconda-client&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the &lt;code&gt;conda-build&lt;/code&gt; package installed, you can run &lt;code&gt;conda build .&lt;/code&gt; where the
&lt;code&gt;.&lt;/code&gt; is the path to the directory containing &lt;code&gt;meta.yaml&lt;/code&gt; file. As part of build
process conda will create new environments for both the build and test phases,
preventing the packages in your current environment from interfering with the
build process and ensuring you have specified all requirements. The build phase
prepares the package for upload to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://anaconda.org/&quot;&gt;Anaconda Cloud&lt;/a&gt;, which is handled by
anaconda client. If you have already provided credentials the upload &lt;em&gt;should&lt;/em&gt;
be automatic, though on failure the error messages include steps to complete
the upload.&lt;/p&gt;
&lt;p&gt;With your package uploaded to the Anaconda Cloud, anybody can install it by specifying your
repository. You can install my plugin with the command&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; conda&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; malramsay&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; hoomd-harmonic-force&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;although I wouldn&#39;t recommend using it at this stage. That said, contributions
are most welcome.&lt;/p&gt;
&lt;p&gt;While the process of packaging is difficult, I have hopefully made it somewhat
more approachable. The good thing is that once you have a &lt;code&gt;meta.yaml&lt;/code&gt; file for
a project, maintenance will mostly be updates of version numbers. As an added
benefit, installing or updating your software is much simpler for you, and
importantly, anyone else that wants to try it out. For why open source software
if you don&#39;t intend for someone else to actually use it.&lt;/p&gt;
&lt;h2 id=&quot;entire-meta-yaml&quot;&gt;Entire &lt;code&gt;meta.yaml&lt;/code&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#entire-meta-yaml&quot; aria-label=&quot;Anchor link for: entire-meta-yaml&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I have included the entire &lt;code&gt;meta.yaml&lt;/code&gt; file below for ease of copying.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; meta.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ackage&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  n&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ame&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;oomd-harmonic-force&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  v&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ersion&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0.1.7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;bout&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  h&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ome&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ttps://github.com/malramsay64/hoomd-harmonic-force&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  l&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;icense&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; M&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;IT&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  l&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;icense_file&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; L&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ICENSE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ource&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  g&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;it_url&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ttps://github.com/malramsay64/hoomd-harmonic-force.git&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  g&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;it_rev&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; v&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;0.1.7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;equirements&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  b&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;uild&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ython 3.6.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; s&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;etuptools&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;umpy 1.14.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;oomd 2.3.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;udatoolkit 8.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;make &amp;gt;=2.8.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  r&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;un&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ython 3.6.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;umpy 1.14.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;oomd 2.3.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;udatoolkit 8.*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;uild&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;cript&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ake clean &amp;amp;&amp;amp; make install&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  n&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;umber&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;est&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  i&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;mports&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; h&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;oomd.harmonic_force&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  r&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;equires&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ytest&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  s&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ource_files&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;est/*&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  c&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ommands&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; m&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ake test&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</content>
	</entry>
	<entry xml:lang="en">
		<title>Scraping Data from Facebook</title>
		<published>2018-05-09T00:00:00+00:00</published>
		<updated>2018-05-09T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/scraping-data-from-facebook/"/>
		<id>https://malramsay.com/post/scraping-data-from-facebook/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/scraping-data-from-facebook/">&lt;p&gt;Competition is a strange thing
making you suddenly interested in the most unusual of problems
It has become a tradition of The Lancer Band,
of which I am a member,
to produce a video as part of our ANZAC Day commemorations.
These videos have been highly successful
garnering millions of views on Facebook and
with some choice communications throughout the rest of the year
have resulted in a commendable social media following.&lt;/p&gt;
&lt;p&gt;After a successful drive to raise the number of page likes this year,
we became more interested in how we compared to other similar pages,
from other service bands in Australia,
to other units of the Australian Army.
Rather than manually collating all this data manually,
I looked for an automated solution for this process,
both to have up to date data,
and to store historical data
allowing for comparisons with these pages over time.&lt;/p&gt;
&lt;p&gt;The method the internet recommends for getting data from Facebook
is through their &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://developers.facebook.com/docs/graph-api&quot;&gt;GraphAPI&lt;/a&gt;,
a method of using http requests query specific data.
The first step in using the GraphAPI is getting an access token,
the simplest method though logging into the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://developers.facebook.com/tools/explorer/&quot;&gt;GraphAPI Explorer&lt;/a&gt;,
a web based tool for testing GraphAPI.
In the process of getting an access token from the GraphAPI Explorer,
a prompt will ask for the permissions granted to the access token.
You can uncheck all the permissions since they are not required for this example.
The more permissions granted to an access token,
the more data from the GraphAPI is accessible,
here interested in the &lt;code&gt;fan_count&lt;/code&gt;, which is the number of users who like the page.
Documentation for this value,
and all the other values can are available in the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://developers.facebook.com/docs/graph-api/reference/page&quot;&gt;page documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With the page and data field to query and the access token,
it is possible to query the GraphAPI in python
using the requests package as in the code example below.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;page&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;thelancerband&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;token&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;lt;access_token&amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ret&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;https://graph.facebook.com/&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;{&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; params&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;fan_count&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;access_token&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; token&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ret&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;fan_count&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that you will need to replace &lt;code&gt;&amp;lt;access_token&amp;gt;&lt;/code&gt; with the token
generated from the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://developers.facebook.com/tools/explorer/&quot;&gt;GraphAPI Explorer&lt;/a&gt;.
At the time of writing &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://facebook.com/thelancerband&quot;&gt;The Lancer Band&lt;/a&gt; has 6546 likes,
which is the value output when I ran the code snippet above.
Hopefully when you are try this the result is somewhat larger.&lt;/p&gt;
&lt;p&gt;While the GraphAPI has a relatively simple interface for querying
many different types of data,
it has a fairly significant drawback for accessing data over periods of time.
The access tokens for the GraphAPI Explorer have an expiry of 1 hour,
meaning nearly every time you want to perform data collection you need a new token.
Programmatically generating access tokens requires the creation of a Facebook application.
While this sounds simple enough,
it requires getting the app reviewed and accepted by Facebook,
far more work than I was willing to put into this.
While these requirements are fair enough for all the personal information
having access to a Facebook profile provides,
I only want to access the publicly available likes data of a page.&lt;/p&gt;
&lt;p&gt;With all the effort required
to go through the &#39;proper&#39; methods to obtain this data,
I looked for alternate approaches.
As far as I can discern coming from the scientific realm,
two of the key packages for collecting and processing data from websites&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://docs.python-requests.org/en/master/&quot;&gt;requests&lt;/a&gt;&lt;/strong&gt; for an interface with http requests and responses&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.crummy.com/software/BeautifulSoup/bs4/doc/&quot;&gt;beautifulsoup4&lt;/a&gt;&lt;/strong&gt; which provides an interface
for extracting information from a returned html/xml document.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We are going to use requests to get the page which has the number of likes contained on it,
then use beautifulsoup4 to extract the number required.&lt;/p&gt;
&lt;p&gt;Finding the web address to use in the request is a case of navigating Facebook manually,
with the number of likes appearing on the community page
having the url [https://facebook.com/pg/thelancerband/community][].
The contents of this page can be downloaded using the code snippet below;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;page_name&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;thelancerband&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;community_page&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;https://facebook.com/pg/&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;{&lt;/span&gt;&lt;span&gt;page_name&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/community&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gets the html,
from which we need to extract the number of likes.
Printing the entire returned page to output&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;community_page&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;gives a huge wall of text.
Using the search functionality to look for instances of &#39;Total Likes&#39;
finds the following code snippet&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;html&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;_3xom&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;6,546&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;div&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;_3xok&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;Total Likes&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The number of likes is in the div element preceding the div containing the text &lt;code&gt;Total Likes&lt;/code&gt;.
Beautifulsoup4 allows us to programmatically traverse the document in a similar logic&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;from&lt;/span&gt;&lt;span&gt; bs4&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt; BeautifulSoup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;document&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; BeautifulSoup&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;community_page&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;html.parser&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Total Likes&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parent&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;previous&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we search for text matching &#39;Total Likes&#39;,
which returns a reference to that location in the document.
The number of likes is the content of the previous div element,
by taking the parent element to select the surrounding div,
then the previous element we extract the value of the previous div element.&lt;/p&gt;
&lt;p&gt;With the total number of likes as string,
we need to reformat it to convert to an integer.
This involves removing the comma as the thousands separator and
the SI prefixes &lt;code&gt;k&lt;/code&gt;, &lt;code&gt;M&lt;/code&gt;, etc. when the likes are in the hundreds of thousands or more.
The quick and dirty method of removing the comma is using &lt;code&gt;str.replace(&#39;,&#39;, &#39;&#39;)&lt;/code&gt;,
though a real project should use the &lt;code&gt;locale&lt;/code&gt; module as described on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://stackoverflow.com/questions/1779288/how-do-i-use-python-to-convert-a-string-to-a-number-if-it-has-commas-in-it-as-th&quot;&gt;stackoverflow&lt;/a&gt;.
A simple fix for the prefixes is installing the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://pypi.org/project/humanfriendly/&quot;&gt;humanfriendly&lt;/a&gt; package,
and using the &lt;code&gt;humanfriendly.parse_size()&lt;/code&gt; function which will handle the prefixes.
This results in the code for getting likes from a list of pages looking like that below.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;from&lt;/span&gt;&lt;span&gt; bs4&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt; BeautifulSoup&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; humanfriendly&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;page_list&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;thelancerband&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; get_num_likes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page_id&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    community_page&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;https://facebook.com/pg/&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;{&lt;/span&gt;&lt;span&gt;page_id&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;/community&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    document&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; BeautifulSoup&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;community_page&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;html.parser&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    page_likes&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; humanfriendly&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse_size&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        document&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Total Likes&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parent&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;previous&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    )&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    return&lt;/span&gt;&lt;span&gt; page_likes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;for&lt;/span&gt;&lt;span&gt; page&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span&gt; page_list&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;The Facebook page for &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;{&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; has &lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;{&lt;/span&gt;&lt;span&gt;get_num_likes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; likes&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This prints the results to the screen,
which is not useful for longer term analysis
although it is easy to see that everything is working as expected.&lt;/p&gt;
&lt;p&gt;There are many different methods for storing this data for later analysis,
from a simple csv file to a sqlite database.
I am most familiar with pandas and HDF5 files,
hence these were my tools of choice
resulting in the data collection loop below.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; pandas&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;for&lt;/span&gt;&lt;span&gt; page&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span&gt; page_list&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    data&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;page_id&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; page&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;likes&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; get_num_likes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;        &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; pandas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Timestamp&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;df&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; pandas&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DataFraame&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;from_records&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;df&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set_index&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;to_hdf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;likes_data.h5&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; format&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;table&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; append&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the &lt;code&gt;tables&lt;/code&gt; package is also a requirement for this code snippet to work.&lt;/p&gt;
&lt;p&gt;This is my first foray into scraping websites for data analysis
which is surprisingly simple with the appropriate tools.
The biggest issue I encountered in this project
was trying to understand the Facebook authentication tokens.
While this is a simple example,
it can easily be extended for different use cases.&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>A guide to setting up remote SSH</title>
		<published>2018-04-09T00:00:00+00:00</published>
		<updated>2018-04-09T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/remote-ssh-configuration/"/>
		<id>https://malramsay.com/post/remote-ssh-configuration/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/remote-ssh-configuration/">&lt;p&gt;In the right (or wrong) hands ssh is a powerful tool
for the remote management of a Unix system.
Most desktop, or workstation distributions of Linux disable
remote access over ssh by default.
The simplest method to check if you have ssh server running on your machine
is to run&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ssh&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; localhost&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If ssh is not installed or running
this will print out a message&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ssh: connect to host localhost port 22: Connection refused&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;most likely indicating that the ssh server is not running.&lt;/p&gt;
&lt;p&gt;Before heading any further in enabling remote access over ssh,
it is a good idea to ensure that you have a strong password
for all of the accounts on the device,
in particular the root account (even better is disabling it).
By enabling remote access for yourself
you are also enabling remote access for anyone else that might want to try and log in.&lt;/p&gt;
&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;a class=&quot;zola-anchor&quot; href=&quot;#installation&quot; aria-label=&quot;Anchor link for: installation&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The package required for installation is in most distributions named &lt;code&gt;openssh-server&lt;/code&gt;.
So for Ubuntu running the command;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; apt&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; openssh-server&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will install the package as appropriate.
Other Linux distributions have different package managers so
use the one for your distribution.
So for fedora, replace &lt;code&gt;apt&lt;/code&gt; with &lt;code&gt;dnf&lt;/code&gt; and for CentOS replace &lt;code&gt;apt&lt;/code&gt; with &lt;code&gt;yum&lt;/code&gt;.
On macOS everything is already installed and just needs to be enabled
in System Preferences &amp;gt; Sharing then ensure Remote Login is checked.&lt;/p&gt;
&lt;p&gt;Once the package is installed,
it needs to be both added to the list of packages to run at boot,
and started now to test.&lt;/p&gt;
&lt;p&gt;To start the openssh server, run the below command&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; systemctl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; start&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ssh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which is using the &lt;code&gt;systemd&lt;/code&gt; init system to start the openssh server instance.
For fedora and CentOS, the ssh service instead has the name sshd so run&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; systemctl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; start&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; sshd&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to start the server instance.
Since this is probably a service you want running automatically on boot,
running the command&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; systemctl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; enable&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ssh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will put files in the appropriate places to enable the service on boot.
Other useful commands for &lt;code&gt;systemctl&lt;/code&gt; are &lt;code&gt;stop&lt;/code&gt;, &lt;code&gt;restart&lt;/code&gt;, and &lt;code&gt;disable&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Once the ssh server is running
our command to connect at localhost&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ssh&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; localhost&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;should present a new message like the one below.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;The authenticity of host &amp;#39;localhost (::1)&amp;#39; can&amp;#39;t be established.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ECDSA key fingerprint is ba:ba:b1:4c:5a:e2:aa:40:aa:7f:aa:01:aa:df:aa:47.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Are you sure you want to continue connecting (yes/no)?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In some ways this is like the terms and conditions when you sign up for a website
in that you should care about and understand it,
however, typically you click through (or type yes) without reading.
Since we are connecting to localhost,
which has an IPV6 address of &lt;code&gt;::1&lt;/code&gt;
or IPV4 address of &lt;code&gt;127.0.0.1&lt;/code&gt;
we can trust the key fingerprint.
This is a method of checking that whenever we log into a host over ssh,
the host hasn&#39;t changed which if unexpected
could be indicative of a man-in-the-middle attack or other nefarious actions.&lt;/p&gt;
&lt;p&gt;Now we are able to connect to the machine locally,
we need to work out how to connect from a remote machine.&lt;/p&gt;
&lt;h2 id=&quot;how-do-i-find-my-device&quot;&gt;How do I Find My Device?&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-do-i-find-my-device&quot; aria-label=&quot;Anchor link for: how-do-i-find-my-device&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;With the previous commands we have been passing &lt;code&gt;localhost&lt;/code&gt; to the ssh command,
in the same way we might type &lt;code&gt;google.com&lt;/code&gt; into a web browser.
These names are a human readable format for addressing devices,
which map to IPV6 or IPV4 addresses computers read to initiate a connection.&lt;/p&gt;
&lt;p&gt;To find the name of the computer we are working on,
also known as the hostname,
we can use the &lt;code&gt;hostname&lt;/code&gt; command.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; hostname&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;f&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;lovelace.staff.sydney.edu.au&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here the &lt;code&gt;-f&lt;/code&gt; gives the full hostname of the device you are using,
with a &lt;code&gt;-s&lt;/code&gt; flag also supported,
however we need to full hostname.&lt;/p&gt;
&lt;p&gt;With the full hostname,
we know the name of our device,
and we know that our device is aware of it&#39;s hostname.
This doesn&#39;t necessarily mean that any other devices know what ours is called.
The process of taking a hostname and turning it into an address
the computer can use is known as DNS,
which is provided by name servers.
Typically in organisations there are multiple levels of name servers,
which will handle both internal names,
like the hostname of our device,
and external names
like &lt;code&gt;github.com&lt;/code&gt; or &lt;code&gt;wikipedia.org&lt;/code&gt;.
Where a device is querying an external name server,
it will not be able to resolve local names.
To test this we can use the name server lookup &lt;code&gt;nslookup&lt;/code&gt; command.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ nslookup lovelace.staff.sydney.edu.au&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Server:         172.18.240.210&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Address:        172.18.240.210#53&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Name:   lovelace.staff.sydney.edu.au&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Address: 10.65.205.200&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tells us that the server at the ip address &lt;code&gt;172.18.240.210&lt;/code&gt;
which my laptop is using as the default name server,
knows that &lt;code&gt;lovelace.staff.sydney.edu.au&lt;/code&gt; exists at the address &lt;code&gt;10.65.205.200&lt;/code&gt;.
If we instead used one of the large public DNS servers
provided by Google (&lt;code&gt;8.8.8.8&lt;/code&gt;) or CloudFlare (&lt;code&gt;1.1.1.1&lt;/code&gt;)
we will instead get an error message.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ nslookup lovelace.staff.sydney.edu.au 1.1.1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Server:         1.1.1.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Address:        1.1.1.1#53&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;** server can&amp;#39;t find lovelace.staff.sydney.edu.au: SERVFAIL&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;setting-the-hostname&quot;&gt;Setting the hostname&lt;a class=&quot;zola-anchor&quot; href=&quot;#setting-the-hostname&quot; aria-label=&quot;Anchor link for: setting-the-hostname&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;When going through the initial setup of macOS or linux machines,
there is a step where you give the machine the hostname.
To later change the hostname requires the editing a couple of files.
The file &lt;code&gt;/etc/hostname&lt;/code&gt; stores the hostname of the machine,
which will typically be the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://en.wikipedia.org/wiki/Fully_qualified_domain_name&quot;&gt;Fully Qualified Domain Name&lt;/a&gt; (FQDN) of the host.
To actually change the hostname a reboot is required,
as this file is read during the boot sequence.
Before you do reboot,
there is another file &lt;code&gt;/etc/hosts&lt;/code&gt; that requires editing.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;/etc/hosts&lt;/code&gt; file is the fist place that is checked
when looking to resolve a domain name
and is sometimes used as a way to block websites
or just to resolve hostname.
We want to our domain name to resolve to &lt;code&gt;127.0.0.1&lt;/code&gt; and &lt;code&gt;::1&lt;/code&gt;
so that services like web servers don&#39;t get confused.
Ensure the following lines (with the appropriate hostname) are in the &lt;code&gt;/etc/hosts&lt;/code&gt; file&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;127.0.0.1   lovelace lovelace.malramsay.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;::1         lovelace lovelace.malramsay.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This sets the ip address we want the hostname to resolve to,
which on the first line is &lt;code&gt;127.0.0.1&lt;/code&gt;,
followed by a list of hostnames,
being the short version &lt;code&gt;lovelace&lt;/code&gt; and the FQDN &lt;code&gt;lovelace.malramsay.com&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-do-i-do-if-i-can-t-lookup-my-hostname&quot;&gt;What do I do if I can&#39;t lookup my hostname?&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-do-i-do-if-i-can-t-lookup-my-hostname&quot; aria-label=&quot;Anchor link for: what-do-i-do-if-i-can-t-lookup-my-hostname&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The reason we use domain names and hostnames
is that they are far easier to remember than a string of numbers.
It is possible to navigate to Google by navigating to the ip address &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://216.58.200.110&quot;&gt;216.58.200.110&lt;/a&gt;
which is the address returned for google.com.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ nslookup google.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Server:         172.18.240.210&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Address:        172.18.240.210#53&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Non-authoritative answer:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Name:   google.com&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Address: 216.58.200.110&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While using the IP address is possible,
it is far more cumbersome to remember and to type.
For a single IP address which we can put in a configuration file,
this isn&#39;t too problematic.&lt;/p&gt;
&lt;p&gt;To find the ip address of the current host
run the command &lt;code&gt;ip addr&lt;/code&gt;,
which will give output like that below.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ ip addr&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;lo0: flags=8049&amp;lt;UP,LOOPBACK,RUNNING,MULTICAST&amp;gt; mtu 16384&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        inet 127.0.0.1/8 lo0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        inet6 ::1/128&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        inet6 fe80::1/64 scopeid 0x1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;en0: flags=8863&amp;lt;UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST&amp;gt; mtu 1500&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ether 78:4f:43:4d:cc:53&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        inet6 fe80::18c5:93d4:e972:b798/64 secured scopeid 0x7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &amp;gt;      inet 10.16.249.156/21 brd 10.16.255.255 en0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;awdl0: flags=8943&amp;lt;UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST&amp;gt; mtu 1484&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ether b6:54:4e:bd:a7:d7&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        inet6 fe80::b454:4eff:febd:a7d7/64 scopeid 0xa&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;utun0: flags=8051&amp;lt;UP,POINTOPOINT,RUNNING,MULTICAST&amp;gt; mtu 2000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        inet6 fe80::1c95:35f4:acea:1034/64 scopeid 0x10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;en5: flags=8863&amp;lt;UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST&amp;gt; mtu 1500&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ether ac:df:48:00:11:22&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        inet6 fe80::aede:48ff:fe00:1122/64 scopeid 0x9&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;en7: flags=8863&amp;lt;UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST&amp;gt; mtu 1500&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        ether 00:0e:c6:e2:bb:69&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        inet6 fe80::1871:5dc7:1f58:2127/64 secured scopeid 0x12&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &amp;gt;      inet 10.65.205.200/24 brd 10.65.205.255 en7&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The lines we are looking for are those with &lt;code&gt;inet&lt;/code&gt; at the start
which I have denoted with a &lt;code&gt;&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This gives me two IP addresses,
one for the wifi and another for the wired ethernet connection.
You may have noticed that the &lt;code&gt;10.65.205.200&lt;/code&gt; address matched that obtained
from the &lt;code&gt;nslookup&lt;/code&gt; of my hostname above.
This tells me that the name server is directing
queries of my hostname back to my device.&lt;/p&gt;
&lt;p&gt;Now we have an ip address,
how do we make the computer remember it
when the name server doesn&#39;t.&lt;/p&gt;
&lt;h2 id=&quot;remembering-ip-addresses&quot;&gt;Remembering IP addresses&lt;a class=&quot;zola-anchor&quot; href=&quot;#remembering-ip-addresses&quot; aria-label=&quot;Anchor link for: remembering-ip-addresses&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The DNS lookup of IP addresses from hostnames works on
the institutional network I have access to at the University of Sydney.
You will likely not have this same configuration,
so there are some alternate methods
for getting your computer to remember ip addresses.&lt;/p&gt;
&lt;h3 id=&quot;ssh-configuration-file&quot;&gt;SSH configuration file&lt;a class=&quot;zola-anchor&quot; href=&quot;#ssh-configuration-file&quot; aria-label=&quot;Anchor link for: ssh-configuration-file&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The simplest method of getting the computer
to remember an ip address for you is the ssh config file &lt;code&gt;~/.ssh/config&lt;/code&gt;.
This approach only works when connecting using ssh,
however, it is useful for setting all the parameters for an ssh connection.
By putting the code snippet below in &lt;code&gt;~/.ssh/config&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Host lovelace&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Hostname 10.65.205.200&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    User malcolm&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;running the command &lt;code&gt;ssh lovelace&lt;/code&gt;,
is equivalent to running &lt;code&gt;ssh malcolm@10.65.205.200&lt;/code&gt;
and is much easier to both type and remember (and tab-complete).
Additional useful parameters for the configuration are&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LocalForward&lt;/code&gt;/&lt;code&gt;RemoteForward&lt;/code&gt; for port forwarding,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Port&lt;/code&gt; when using a non-standard port, and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IdentityFile&lt;/code&gt; for using specific private keys.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;ssh_config&lt;/code&gt; man pages (&lt;code&gt;man ssh_config&lt;/code&gt;) also contain
all the parameters available and information about each of them.&lt;/p&gt;
&lt;h3 id=&quot;hosts-file&quot;&gt;Hosts file&lt;a class=&quot;zola-anchor&quot; href=&quot;#hosts-file&quot; aria-label=&quot;Anchor link for: hosts-file&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Earlier we modified the &lt;code&gt;/etc/hosts&lt;/code&gt; file to change the hostname of our system.
We can also use it to remember the ip address of other systems.
This is not recommended for managing many devices, use DNS for that,
for a small number of systems we can use use this file for the translation of
domain names to ip addresses.
Instead of using the ip address of localhost,
we use the ip address we want the hostname to resolve to as shown below.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;10.65.205.200   lovelace.malramsay.com&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The advantage this approach has over the ssh configuration
is that it will also resolve the hostname in the web browser.
This also makes it much easier to access web services
like jupyter notebooks on the remote host.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;This should be sufficient to get started using SSH within the same network.
To access the service over the internet,
where possible use the institutional VPN,
or set up your own.&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Adventures in the Python Visualisation Landscape</title>
		<published>2018-03-17T00:00:00+00:00</published>
		<updated>2018-03-17T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/adventures-in-visualisation/"/>
		<id>https://malramsay.com/post/adventures-in-visualisation/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/adventures-in-visualisation/">&lt;p&gt;Calling the current python visualisation landscape fragmented would probably be an understatement,
since itself requires a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://youtu.be/FytuB8nFHPQ?t=3m53s&quot;&gt;visualisation&lt;/a&gt; to even begin to comprehend.
For various reasons I have been unhappy with the tool I was using for visualisation at that time
and I have been searching for the one visualisation package to rule them all (spoiler: it doesn&#39;t exist)&lt;/p&gt;
&lt;p&gt;About a year ago I was using Matplotlib for all my figures,
which enabled me to create anything I wanted,
usually with a stackoverflow answer giving me a working example to adapt.
Problem was, nearly everything required a stackoverflow post or the documentation,
making the process of building these figures slow and tedious.
Around the same time I was getting fed up with Matplotlib,
I found Jake VanderPlas&#39; PyCon talk on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://youtu.be/FytuB8nFHPQ?t=3m53s&quot;&gt;The Python Visualisation Landscape&lt;/a&gt;
which has since acted as both a map and a list of achievements to unlock.&lt;/p&gt;
&lt;h3 id=&quot;altair-1-2&quot;&gt;Altair 1.2&lt;a class=&quot;zola-anchor&quot; href=&quot;#altair-1-2&quot; aria-label=&quot;Anchor link for: altair-1-2&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Somewhat naturally the first package I looked at for improving my visualisation workflow was
Altair which was at the time a 1.2 release.
At the time I thought it was fine for simple figures,
however my data was not formatted to make the most of what Altair could offer,
which as a relative newbie to pandas was frustrating.
Another sticking point with Altair was working out how to customise figures,
After Matplotlib, I was expecting to search what I wanted to in Google
and have appropriate stack overflow answer on the first page of results.
With Altair being a new package, this how-to style documentation didn&#39;t exist,
which meant I never worked out how to customise the figure.
The most frustrating of the default settings was the axis labels
which use the SI Prefix for large numbers
i.e. 1Mm for $1 \times 10^6$  and 1no for $1 \times 10^{-9}$,
something which I never worked out how to change at the time.
Another problem I had with Altair (and Matplotlib) was the lack of interactivity.
I was in the early stages of data investigation
which required both a high level overview of the trends,
while also being able to look at smaller regions in more detail.
Having to constantly change axis ranges was
making it slow and frustrating to create a figure with the appropriate view.&lt;/p&gt;
&lt;h3 id=&quot;bokeh&quot;&gt;Bokeh&lt;a class=&quot;zola-anchor&quot; href=&quot;#bokeh&quot; aria-label=&quot;Anchor link for: bokeh&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;With an emphasis on interactivity,
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://bokeh.pydata.org/en/latest/&quot;&gt;Bokeh&lt;/a&gt; was the tool I was looking for to investigate data on different scales.
With the ability to generate figures in a notebook,
for quick visualisations of datasets to understand the data,
and compare it with previous studies,
and also as a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://bokeh.pydata.org/en/latest/docs/user_guide/server.html&quot;&gt;Bokeh server&lt;/a&gt; application,
to perform standard analyses on large volumes of data,
like ensuring a simulation is running properly.
Despite having these interactive visualisations,
bokeh is lacking in the same way as Matplotlib,
it takes a long time to specify everything required to create the figures.
While there was a start on a high level plotting interface in the form of &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/bokeh/bkcharts&quot;&gt;bkcharts&lt;/a&gt;,
the code is now unmaintained and directs users to Holoviews.&lt;/p&gt;
&lt;h3 id=&quot;holoviews&quot;&gt;Holoviews&lt;a class=&quot;zola-anchor&quot; href=&quot;#holoviews&quot; aria-label=&quot;Anchor link for: holoviews&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Like Altair, &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://holoviews.org&quot;&gt;Holoviews&lt;/a&gt; is a declarative plotting interface,
providing a quick and simple interface for constructing figures.
Holoviews is structures around the idea of
describing data on creation of the dataset,
rather than the construction of the figure,
allowing for simple figure definitions.
Rather than being a library that actually creates a figure,
Holoviews performs the reasoning about the dataset
and passing the rendering off to Bokeh, Matplotlib, or Plotly.
I initially saw this as a big strength of Holoviews,
that I could generate interactive visualisations in bokeh,
change the output to Matplotlib and have a configurable figure for publication.
In practice this is not so simple,
with some modifications to plot style parameters
changing between the different output formats
and having a limited range of customisations.&lt;/p&gt;
&lt;p&gt;The drawback of having this special annotated data object
is that you lose all the flexibility of having a pandas DataFrame
and the vast array of operations that allows.
This is important to me
because my field of science has a long history of
researchers taking some quantities and
combining them to give a describable temperature dependence.
This lack of flexibility led me back to Altair,
this time version 2.0.&lt;/p&gt;
&lt;h3 id=&quot;altair-2-0&quot;&gt;Altair 2.0&lt;a class=&quot;zola-anchor&quot; href=&quot;#altair-2-0&quot; aria-label=&quot;Anchor link for: altair-2-0&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Why Altair again?
When I first tried Altair I was approaching it from Matplotlib,
with its extensive documentation
in the form of the technical reference and the numerous how to guides.
This time I was approaching it from Bokeh and Holoviews,
both of which are relatively new libraries with some teething problems.
As a consequence I had become much better at problem solving issues
and navigating technical reference materials.
Another major moment of discovery was making the connection
that since Altair implements the Vega-Lite specification,
I should have a look at the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://vega.github.io/vega-lite/docs/&quot;&gt;Vega-Lite documentation&lt;/a&gt;.
This turned out to be particularly helpful
because the Vega-Lite documentation
is currently more extensive than for &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://altair-viz.github.io/index.html&quot;&gt;Altair&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It was in reading the Vega-Lite documentation
that I finally understood how to use transform functions in Altair.
These are a set of functions that perform computations on the input dataset to generate the resulting figure.
This allows me to have a single canonical dataset,
with data transformations like ratios of two quantities tied to the figure,
rather than following awkwardly named variables around.
One example of these functions is
to make the data in the cars dataset
useful for the majority of the world&#39;s population
by converting the units as part of the figure definition.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; altair&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; as&lt;/span&gt;&lt;span&gt; alt&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;from&lt;/span&gt;&lt;span&gt; vega_datasets&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cars&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; data&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cars&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;chart&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; alt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Chart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cars&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mark_circle&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transform_filter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    alt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;expr&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;datum&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Miles_per_Gallon&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transform_calculate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Fuel Economy (L/100 km)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;235.2 / datum.Miles_per_Gallon&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transform_calculate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Weight (kg)&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;datum.Weight_in_lbs * 0.45&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;chart&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    x&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Fuel Economy (L/100 km):Q&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    y&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Weight (kg):Q&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    size&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Acceleration:Q&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/img/altair-cars-metric.svg&quot; alt=&quot;Fuel economy (L/100km) vs weight (kg) from the cars dataset.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This allows for only storing the fundamental values in the dataset
and being able to compute derived values as part of the figure,
something that is particularly useful in my workflow.&lt;/p&gt;
&lt;p&gt;This computing of values,
also extends to the computation of histograms,
complete with shortened notation.
Using the chart object from above it is possible to easily create a histogram&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;chart&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mark_bar&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    x&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;alt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;X&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Fuel Economy (L/100 km):Q&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; bin&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    y&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;count():Q&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/img/altair-cars-hist.svg&quot; alt=&quot;Histogram of the fuel economy in the cars dataset.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Where setting &lt;code&gt;bin=True&lt;/code&gt; will create bins with the default parameters,
and the &lt;code&gt;count():Q&lt;/code&gt; on the &lt;code&gt;y&lt;/code&gt; axis counts the elements in each bin.
Instead of &lt;code&gt;count()&lt;/code&gt; it is also possible to perform other &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://vega.github.io/vega-lite/docs/aggregate.html#ops&quot;&gt;aggregations&lt;/a&gt;,
like computing the mean of a column.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;chart&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;mark_bar&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    x&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;alt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;X&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Fuel Economy (L/100 km):Q&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; bin&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;True&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    y&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;mean(Weight (kg)):Q&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/img/altair-cars-weight.svg&quot; alt=&quot;Histogram of the fuel economy in the cars dataset.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For a more comprehensive view of using Altiar,
have a look at either the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://altair-viz.github.io/gallery/index.html&quot;&gt;Example Gallery&lt;/a&gt;,
or a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://altair-viz.github.io/case_studies/exploring-weather.html&quot;&gt;case study&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Each of the visualisation libraries in python
have their own strengths and weaknesses,
types of visualisations they excel at,
and others you wouldn&#39;t want to try.
For me, while Altair does still have a some quirks,
most notably in the handling of &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/altair-viz/altair/issues/249&quot;&gt;large datasets&lt;/a&gt;,
and a somewhat complicated method of &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/altair-viz/altair/issues/585&quot;&gt;setting titles&lt;/a&gt;,
it provides a simple and intuitive interface to data
which at the time of writing makes it the first tool I will reach for
to understand a dataset.&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Creating an HDF5 Bomb</title>
		<published>2018-03-15T00:00:00+00:00</published>
		<updated>2018-03-15T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/creating-hdf5-bomb/"/>
		<id>https://malramsay.com/post/creating-hdf5-bomb/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/creating-hdf5-bomb/">&lt;p&gt;You may have heard of a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://en.wikipedia.org/wiki/Zip_bomb&quot;&gt;zip bomb&lt;/a&gt; or other decompression &#39;bombs&#39;,
which have the basic premise of containing a large volume of highly redundant data
that when decompressed takes up more resources than the system can handle.
Within the HDF5 file format there is &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://support.hdfgroup.org/HDF5/faq/compression.html&quot;&gt;support for compression&lt;/a&gt;,
an excellent tool for reducing file sizes,
however also ripe for exploitation.
This &#39;issue&#39;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt; of a file containing far more data expected, whether accidental or malicious,
is not limited to HDF5 files, any filetype supporting compression is susceptible.&lt;/p&gt;
&lt;p&gt;How do you actually create one a decompression bomb.
The simplest method in python is to create a pandas DataFrame
comprising lots of strings which are all the same.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; pandas&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; as&lt;/span&gt;&lt;span&gt; pd&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;large_number&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1_000_000&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;df&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; pd&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DataFrame&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;evil&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;😈&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;large_number&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using the &lt;code&gt;sys.getsizeof&lt;/code&gt; function we can find
the size in memory of this DataFrame&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; sys&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getsizeof&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sys&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getsizeof&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;df&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; /&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1024&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; /&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1024&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;  #&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; bytes / kilobytes / megabytes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which turns out to be 840 MB,
10 times larger than a DataFrame with the same number of integers.
This significant overhead of using strings is because
each element in the DataFrame contains all the storage overhead of a python object,&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#2&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;
rather than each column for numerical types,
and is the main reason I chose strings for this diabolical construct.&lt;/p&gt;
&lt;p&gt;Now we have a large, low entropy dataset we need to save it to disk.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;with&lt;/span&gt;&lt;span&gt; pd&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;HDFStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;dataset.h5&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; mode&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; complevel&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;9&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; complib&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;bzip2&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; as&lt;/span&gt;&lt;span&gt; dst&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    dst&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; df&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A number of keyword arguments are set in opening the HDFStore file handle,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mode=&#39;w&#39;&lt;/code&gt; ensures that a file with the same name is overwritten if it exists, making testing this
out much simpler.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;complevel=9&lt;/code&gt; sets the compression to the maximum possible not caring about the processing
requirements of doing so.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;complib=&#39;bzip2&#39;&lt;/code&gt; sets the compression library to &lt;code&gt;bzip2&lt;/code&gt; which had significantly better
compression ratios than the default &lt;code&gt;zlib&lt;/code&gt; in my testing of this.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally the &lt;code&gt;append&lt;/code&gt; function has been used to write the DataFrame to disk
since we want to create a dataframe that doesn&#39;t fit in memory,
which will require appending to the file numerous times.
With these options, the 840 MB DataFrame is a 2.2 MB HDF5 file on disk,
a compression ratio greater than 200.&lt;/p&gt;
&lt;p&gt;With all the pieces in place we can now construct our file.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;num_iters&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 20&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;with&lt;/span&gt;&lt;span&gt; pd&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;HDFStore&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;dataset.h5&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; mode&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; complevel&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;9&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; complib&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;bzip2&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; as&lt;/span&gt;&lt;span&gt; dst&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    for&lt;/span&gt;&lt;span&gt; _&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;num_iters&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        dst&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; df&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This generates a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://drive.google.com/open?id=1tlr00OFEuKMkSz0slczInh3211-ZRBIz&quot;&gt;small unsuspecting 42 MB file&lt;/a&gt;&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#3&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;
which when loaded as a pandas DataFrame becomes an 18 GB object in memory,
a compression ratio of 440.&lt;/p&gt;
&lt;p&gt;While this is a little fun and devious,
it does highlight the importance of thinking about how we represent the data we are processing,
in particular text data.
I originally came across this HDF5 bomb by accident,
leaving a field as text when it should have been a category.
In this devious case presented, using the type &#39;category&#39; in the DataFrame&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;df[&amp;#39;evil&amp;#39;] = df[&amp;#39;evil&amp;#39;].astype(&amp;#39;category&amp;#39;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;our 840 MB DataFrame becomes 10 MB,
and there are no issues storing 20 of them in memory.
While it may be cool to use big data tools like Spark, Dask, or Hadoop
sometimes the simplest approach is to make the big data small.&lt;/p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;/sup&gt;
&lt;p&gt;The compression is working exactly as intended, it just hides the true size of the underlying data.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;2&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;/sup&gt;
&lt;p&gt;From interrogating the size of the DataFrame using either &lt;code&gt;sys.getsizeof(df)&lt;/code&gt; or &lt;code&gt;df.memeory_useage(deep=True)&lt;/code&gt; it appears that the memory is allocated for each object. When querying the individual objects using &lt;code&gt;id&lt;/code&gt;, they all return the same value, which is the same as just the string. I don&#39;t know what is going on and would be happy for someone to point me to a good resource.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;3&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;/sup&gt;
&lt;p&gt;Of course you are going to download a file some random stranger on the internet tells you is going to crash your python interpreter.&lt;/p&gt;
&lt;/div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>The Perils of Packaging in Python</title>
		<published>2018-01-07T00:00:00+00:00</published>
		<updated>2018-01-07T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/perils-of-packaging/"/>
		<id>https://malramsay.com/post/perils-of-packaging/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/perils-of-packaging/">&lt;p&gt;There are many guides to packaging a python application,
including the official &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://packaging.python.org/&quot;&gt;Python Packaging User Guide&lt;/a&gt;.
While these guides offer step by step instructions for
deploying a simple application,
when deviating from a guide there can be unexpected problems.&lt;/p&gt;
&lt;p&gt;The application I have been trying to deploy is &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/malramsay64/statdyn-analysis&quot;&gt;this one&lt;/a&gt;,
a collection of tools to assist in the analysis of
molecular dynamics trajectories for my PhD.
Most of the code I have written is python,
with small sections of Cython code for performance.
I have been running automated testing on Travis-CI
and have made many different attempts at deploying the package
to both &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://anaconda.org/&quot;&gt;Anaconda Cloud&lt;/a&gt; and &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://pypi.python.org/pypi&quot;&gt;PyPI&lt;/a&gt;.
I have been trying to have a deployment that when a git tag is pushed to GitHub,
Travis will test, build and upload both the Anaconda and PyPI packages
for Linux and macOS.&lt;/p&gt;
&lt;p&gt;Rather than document what I have found to be the &#39;perfect&#39; method,
something which I still don&#39;t have,
this is a collection of my failures
that will hopefully prevent someone else making the same mistakes.&lt;/p&gt;
&lt;h2 id=&quot;packaging-and-deploying&quot;&gt;Packaging and Deploying&lt;a class=&quot;zola-anchor&quot; href=&quot;#packaging-and-deploying&quot; aria-label=&quot;Anchor link for: packaging-and-deploying&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id=&quot;versioning&quot;&gt;Versioning&lt;a class=&quot;zola-anchor&quot; href=&quot;#versioning&quot; aria-label=&quot;Anchor link for: versioning&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;In deploying an application a version scheme provides
an indication of the changes made,
especially where a semantic versioning scheme is used.
To make the versioning process simpler,
I have been using git tags as the &#39;true&#39; version number.
To get the version number in the setup
I have been using the &lt;code&gt;setuptools_scm&lt;/code&gt; package.
The versioning scheme that best fit how I have been thinking about
release versions is the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.python.org/dev/peps/pep-0440/#post-releases&quot;&gt;post-release&lt;/a&gt; scheme,
using&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;use_scm_version&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;version_scheme&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;post-release&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The default state of setuptools_scm is to
only include the dirty flag when files committed in the project have changed,
rather than any untracked file in the directory.
This change to a post release scheme also changed this default,
meaning that when compiling on Travis every commit was dirty.
This was fixed by also specifying the local version scheme&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;use_scm_version&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;version_scheme&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;post-release&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;                 &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;local_scheme&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;dirty-tag&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While having a hash after the version number is not a problem for me,
it is a large problem when trying to upload packages to PyPI,
which rejects it with the error&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;HTTPError: 400 Client Error: version: Cannot use PEP 440 local versions. for url: https://upload.pypi.org/legacy/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;travis&quot;&gt;Travis&lt;a class=&quot;zola-anchor&quot; href=&quot;#travis&quot; aria-label=&quot;Anchor link for: travis&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;While Travis appears to have good support for uploading to PyPI,
having a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.travis-ci.com/user/deployment/pypi/&quot;&gt;deployment target&lt;/a&gt;,
as far as I can tell it &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/travis-ci/dpl/issues/377&quot;&gt;doesn&#39;t support non alphanumeric
passwords&lt;/a&gt;.
Unfortunately I was unable to get some alternative methods of including a password to work.
Authentication information for uploading to PyPI can be in the &lt;code&gt;~/.pypirc&lt;/code&gt; file,
however when trying to write this file using variable expansion&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;echo&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;[pypi]\nusername=&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;PYPI_USERNAME&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;\npassword=&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;PYPI_PASSWORD&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ~/.pypirc&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;resulted in the file&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[pypi]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;username=$PYPI_USERNAME&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;password=$PYPI_PASSWORD&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;with neither of the variables being expanded.
I quickly gave up on that approach
since it worked in every shell I tested it on locally.&lt;/p&gt;
&lt;p&gt;A solution that works is to use [twine][twine]
which along with generally being more secure
is able to read the environment variables
&lt;code&gt;TWINE_USERNAME&lt;/code&gt; and &lt;code&gt;TWINE_PASSWORD&lt;/code&gt; to authenticate.&lt;/p&gt;
&lt;h3 id=&quot;installing-from-pip&quot;&gt;Installing from Pip&lt;a class=&quot;zola-anchor&quot; href=&quot;#installing-from-pip&quot; aria-label=&quot;Anchor link for: installing-from-pip&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Once a package is uploaded to PyPI it needs to be installable,
i.e. running&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; pip&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;packag&lt;/span&gt;&lt;span&gt;e&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;actually works,
even in a clean environment.
A sample &lt;code&gt;setup.py&lt;/code&gt; file for a python package typically looks something like the one below
(&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://stackoverflow.com/questions/32528560/using-setuptools-to-create-a-cython-package-calling-an-external-c-library&quot;&gt;1&lt;/a&gt;,
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://stackoverflow.com/questions/35497572/using-python-setuptools-to-put-cython-compiled-pyd-files-in-their-original-folde&quot;&gt;2&lt;/a&gt;,
&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://cython.readthedocs.io/en/latest/src/quickstart/build.html&quot;&gt;3&lt;/a&gt;).&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; setup.py&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;from&lt;/span&gt;&lt;span&gt; setuptools&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt; setup&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; Extension&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;from&lt;/span&gt;&lt;span&gt; Cython&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Build&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; import&lt;/span&gt;&lt;span&gt; cythonize&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; numpy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;my_extensions&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;Extension&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;...&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; include_dirs&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;numpy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get_include&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;setup&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    name&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;...&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    ...&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    setup_requires&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Cython&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;numpy&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    install_requires&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;numpy&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    ...&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;    ext_modules&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;cythonize&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;my_extensions&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Problem is, when installing into a new environment via pip
the install fails as reading the &lt;code&gt;setup.py&lt;/code&gt; file requires
both Cython and numpy to be installed as they are imported at the top of the file.
This is a significant problem with python packaging
for which a solution has been accepted &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.python.org/dev/peps/pep-0518/&quot;&gt;PEP 518&lt;/a&gt; and work is in [progress][pep 518 progress].
In meantime, distributing a wheel is a solution to this problem.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Update 2018-08-12&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The release of pip 10.0 introduced support for a &lt;code&gt;pyproject.toml&lt;/code&gt; file in which installation
dependencies can be specified. A &lt;code&gt;pyproject.toml&lt;/code&gt; for the above project would look like:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; pyproject.toml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;build-system&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;requires&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;setuptools&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;wheel&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;numpy&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Cython&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;wheels&quot;&gt;Wheels&lt;a class=&quot;zola-anchor&quot; href=&quot;#wheels&quot; aria-label=&quot;Anchor link for: wheels&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I have included wheels as their own section
since I feel they are especially egregious.
The PyPI &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://packaging.python.org/tutorials/distributing-packages/#wheels&quot;&gt;documentation&lt;/a&gt; tells you that
&quot;You should also create a wheel for your project.&quot;
Well sure, I will just follow the instructions in the docs&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; setup.py&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; bdist_wheel&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; twine&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; upload&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; dist/sdanalysis-0.4.4.post5-cp36-cp36m-linux_x86_64.whl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Uploading&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; distributions&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; to&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://upload.pypi.org/legacy/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Enter&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; your&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; username:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; malramsay64&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Enter&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; your&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; password:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Uploading&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; sdanalysis-0.4.4.post5-cp36-cp36m-linux_x86_64.whl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;HTTPError:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 400&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Client&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Error:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Binary&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; wheel&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;sdanalysis-0.4.4.post5-cp36-cp36m-linux_x86_64.whl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; has&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; an&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; unsupported&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; platform&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; tag&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;linux_x86_64&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; for&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; url:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; https://upload.pypi.org/legacy/&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Huh???
I also think it is great that I have to wait for the package to upload to get the error message.&lt;/p&gt;
&lt;p&gt;Some googling later...&lt;/p&gt;
&lt;p&gt;I need to use &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/pypa/auditwheel&quot;&gt;auditwheel&lt;/a&gt; to create a &lt;code&gt;manylinux1&lt;/code&gt; wheel.
After installing via pip (conda-forge only has auditwheel versions for up to python 3.5)&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; auditwheel&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; repair&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; dist/sdanalysis-0.4.4.post5-cp36-cp36m-linux_x86_64.whl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Repairing&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; sdanalysis-0.4.4.post5-cp36-cp36m-linux_x86_64.whl&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;usage:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; auditwheel&lt;/span&gt;&lt;span&gt; [-h&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;-V&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;-v&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; command ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;auditwheel:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; error:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; cannot&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; repair&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;dist/sdanalysis-0.4.4.post5-cp36-cp36m-linux_x86_64.whl&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; to&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;manylinux1_x86_64&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; ABI&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; because&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; of&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; the&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; presence&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; of&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; too-recent&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; versioned&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; symbols.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; You&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ll need to&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;compile the wheel on an older toolchain.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some more googling and reading documentation ...&lt;/p&gt;
&lt;p&gt;From the auditwheel README:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But in general, building manylinux1 wheels requires running on a CentOS5 machine, so we recommend using the pre-built manylinux Docker image.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; docker&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; `&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;:/io&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; quay.io/pypa/manylinux1_x86_64&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /bin/bash&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Oh easy, now I have to set this up to build in a docker container...&lt;/p&gt;
&lt;p&gt;For a great example of building a manylinux1 wheel,
I can suggest checking out the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/pypa/python-manylinux-demo&quot;&gt;pypa/python-manylinux-demo&lt;/a&gt; repository.
Unfortunately it is not linked from the python packaging documentation
and so is not that simple to find.&lt;/p&gt;
&lt;p&gt;Packaging in python is hard.
I have over 70 builds this week on travis
just trying to get everything to actually work.
At the end of all this I still don&#39;t really know what I am doing
other than basically everything I try doesn&#39;t work
and that I shouldn&#39;t try any of the above,
since I already know it won&#39;t work.&lt;/p&gt;
&lt;h2 id=&quot;building-and-testing&quot;&gt;Building and Testing&lt;a class=&quot;zola-anchor&quot; href=&quot;#building-and-testing&quot; aria-label=&quot;Anchor link for: building-and-testing&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;A crucial aspect of deploying a piece of software is
having some confidence that it actually works,
even if that just means it doesn&#39;t break on import.
For my workflow this is an automated process,
with builds running on &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://travis-ci.org/&quot;&gt;Travis-CI&lt;/a&gt; when
a new commit is pushed to Github.
Even using a popular service live Travis didn&#39;t preclude me
from having plenty of failures.&lt;/p&gt;
&lt;h3 id=&quot;travis-python-and-macos&quot;&gt;Travis, Python and macOS&lt;a class=&quot;zola-anchor&quot; href=&quot;#travis-python-and-macos&quot; aria-label=&quot;Anchor link for: travis-python-and-macos&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;I had automated testing set up on Travis-CI for my python module on linux,
however trying to extent this to also using macOS
I found some unusual behaviour.
At first I thought that testing on travis was going to be
as simple as adding it to the list of os&#39;s.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; .travis.yml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;anguage&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ython&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;s&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; l&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;inux&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;sx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However it really wasn&#39;t because the build raised an error
before my code even started running.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; sudo&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; tar&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; xjf&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python-3.6.tar.bz2&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-directory&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;tar:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Unrecognized&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; archive&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; format&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;tar:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Error&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; exit&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; delayed&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; from&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; previous&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; errors.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;The&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; command&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;sudo tar xjf python-3.6.tar.bz2 --directory /&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; failed&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; and&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; exited&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; with&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; during&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It turns out that on Travis-CI,
the python language is &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.travis-ci.com/user/multi-os/#Python-example-(unsupported-languages)&quot;&gt;unsupported on macOS&lt;/a&gt;.
So I had to bodge some kind of workaround,
especially since the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://docs.travis-ci.com/user/multi-os/#Python-example-(unsupported-languages)&quot;&gt;travis docs&lt;/a&gt; on the issue
assume you are using &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://tox.readthedocs.io/en/latest/&quot;&gt;tox&lt;/a&gt;.
I found were that when using conda to install python,
the simplest workaround is to use &lt;code&gt;language: generic&lt;/code&gt;
and install python through conda for each OS.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; .travis.yml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;anguage&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; g&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;eneric&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;s&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; l&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;inux&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;sx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;efore_install&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; i&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;f [[ $(uname -s) == &amp;quot;Darwin&amp;quot; ]]; then&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;      w&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;get https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O miniconda.sh;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    e&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;lse&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;      w&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;get https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;    f&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;i&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alternatively, when just using the latest version of python
it can be installed using homebrew.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; .travis.yml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;anguage&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ython&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;atrix&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  i&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;nclude&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt; o&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;s&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; l&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;inux&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;      p&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ython&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 3.6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt; o&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;s&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; o&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;sx&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;      l&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;anguage&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; g&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;eneric&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;      b&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;efore_install&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; b&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;rew install python3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;frozen-versions-melt&quot;&gt;Frozen versions melt&lt;a class=&quot;zola-anchor&quot; href=&quot;#frozen-versions-melt&quot; aria-label=&quot;Anchor link for: frozen-versions-melt&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;When deploying with [Conda][conda] the recipe is created in a [meta.yaml][meta.yaml] file,
containing the requirements for each step,
in addition to instructions for building and testing of a package.
A very simple version is shown below specifying a patch version of python (&lt;code&gt;3.6.1&lt;/code&gt;)
to use for both the build and the run phase.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; file: meta.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ackage&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  n&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ame&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;yver_test&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  v&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ersion&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1.0.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;equirements&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  b&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;uild&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ython 3.6.1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  r&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;un&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ython {{ pin_compatible(&amp;#39;python&amp;#39;, max_pin=&amp;#39;x.x.x&amp;#39;) }}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;uild&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt; n&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;umber&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;est&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;  c&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-5&quot;&gt;ommands&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; p&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ython --version&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the version number is adhered to in the build phase,
during the run phase the patch version is ignored,
instead installing the latest patch version of python,
which at the time of writing is 3.6.4.
This is the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/conda/conda-build/issues/2571&quot;&gt;intended behaviour&lt;/a&gt;,
albeit somewhat unexpected.&lt;/p&gt;
&lt;p&gt;The reason that this was a problem for me is that
I came across an &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://bitbucket.org/glotzer/freud/issues/154/conda-package-broken-on-macos-with-python#comment-41977740&quot;&gt;unusual bug&lt;/a&gt; in a conda package
I was trying to use.
When installed on macOS&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  conda&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; create&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; freud&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; glotzer&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; freud&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;importing the module would crash python&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;import freud&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Python&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; error:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; PyThreadState_Get:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; no&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; current&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; thread&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;zsh:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; abort&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;           python&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;import freud&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This environment would use the latest patch version of python (currently 3.6.4)
and a simple workaround was to install &lt;code&gt;python==3.6.1&lt;/code&gt;,
although it did make installing the package more difficult than I would have liked.
I don&#39;t know why this worked, it just did,
although I suspect it has something to do with linking to the python C libraries.&lt;/p&gt;
&lt;h3 id=&quot;when-the-porcelain-stains&quot;&gt;When the porcelain stains&lt;a class=&quot;zola-anchor&quot; href=&quot;#when-the-porcelain-stains&quot; aria-label=&quot;Anchor link for: when-the-porcelain-stains&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://pypi.python.org/pypi/pipenv&quot;&gt;Pipenv&lt;/a&gt; is a packaging manager for python that automates
the creation of virtual environments for python projects
in addition to the packages installed within them.
As Jannis Leidel is quoted in the testimonials;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pipenv is the porcelain I always wanted to build for pip.
However there are always unusual cases when abstracting that can cause issues.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first of these issues I found was in the installation,
I primarily use Conda for my python and virtual environment configuration,&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; pip&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; pipenv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; pipenv&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; numpy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;ERROR:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; virtualenv&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; is&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; not&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; compatible&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; with&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; this&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; system&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; or&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; executable&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, it is required to install pipenv outside of a conda distribution
(&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/pypa/pipenv/issues/699&quot;&gt;issue#699&lt;/a&gt;, &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/pypa/pipenv/issues/288&quot;&gt;issue#288&lt;/a&gt;).
So installation can be performed by running&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /usr/local/bin/pip&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; pipenv&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or with conda 4.4&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; conda&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; deactivate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; pip&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; install&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; pipenv&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One of the major features of Pipenv over other package managers for python
is that it includes hashes of the binaries as part of the version pinning.
This both ensures you are installing exactly the same code on different systems,
and provides security in preventing unknown changes from being installed.
These hashes are collected for all the package versions available on PyPI,
allowing for cross platform and python version use.&lt;/p&gt;
&lt;p&gt;The problem that I came across is that a certain package &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://pypi.python.org/pypi/numpy-quaternion&quot;&gt;numpy-quaternion&lt;/a&gt;
has some nearly identical version numbers,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;numpy-quaternion 2017.11.26.19.0.13&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numpy-quaternion 2017.11.26.19.00.13&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;the second just having an additional zero.
The first of these version numbers contains all the pre-built wheels,
while the second contains the source.
When downloading the hashes,
pipenv downloads the hash for the source distribution
rather than for the wheels.
This means that when trying to install in a new virtualenv
the downloaded package fails the hash comparison&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;THESE PACKAGES DO NOT MATCH THE HASHES FROM Pipfile.lock!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and aborts the install,
incredibly frustrating when the initial install of the package works fine.&lt;/p&gt;
&lt;p&gt;A workaround is to manually update the Pipfile.lock file
with the correct hashes,
which is fine while dependencies are not updated
and the Pipfile.lock file remains unchanged.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Update 2018-01-09&lt;/p&gt;
&lt;p&gt;In a previous revision of the Installing from Pip section
I stated that importing the dependencies within a function call
was a solution to the import problems.
Thanks to &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://www.reddit.com/r/Python/comments/7ope3j/a_guide_on_how_not_to_package_a_python_module/dscmwf5/&quot;&gt;/u/vorpalsmith&lt;/a&gt;
for bringing it to my attention that this isn&#39;t a solution,
and that the success that I observed was a result of installing a wheel
rather than any changes to the code that I made.&lt;/p&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Quaternions for Orientation in MD</title>
		<published>2017-09-13T00:00:00+00:00</published>
		<updated>2017-09-13T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/quaternions-in-md/"/>
		<id>https://malramsay.com/post/quaternions-in-md/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/quaternions-in-md/">&lt;p&gt;In the understanding of the dynamics of a molecule in a Molecular Dynamics (MD) Simulation
the two main properties we use to understand liquid behaviour are
the translational motion and the rotational motion of the atoms or molecules.
These motions reflect changes in the position and the orientation of the molecules.
When calculating these motions in an MD simulation
it makes sense to represent each molecule as;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the position of the Center of Mass (COM), and&lt;/li&gt;
&lt;li&gt;the orientation within the lab reference frame.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This representation allows for quick and simple calculation of
the motion between two separate times.
In the simplest case, conceptually this is just computing&lt;/p&gt;
&lt;p&gt;\[
\begin{aligned}
\text{displacement} &amp;amp;= \text{norm}(\text{position}_2 - \text{position}_1) \\
\text{rotation} &amp;amp;= \text{norm}(\text{orientation}_2 - \text{orientation}_1).
\end{aligned}
\]&lt;/p&gt;
&lt;p&gt;For the displacement,
we can just use a standard Euclidean distance norm.
Which is simple enough to compute.
The rotations are somewhat more difficult.
In a two dimensional system it would be possible to use just the angle,
where the norm is a function keeping the rotation within a sensible bounds, like $(-\pi, \pi]$.
However in three dimensions using angles gets more complicated
and computationally expensive.
The approach that is used in &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://glotzerlab.engin.umich.edu/hoomd-blue/&quot;&gt;hoomd&lt;/a&gt;
and likely many other molecular dynamics programs is
to use quaternions for the representation of orientation.&lt;/p&gt;
&lt;p&gt;This post aims to give an understanding of quaternions
and how to use them to compute a rotation
from one orientation to another.&lt;/p&gt;
&lt;h2 id=&quot;orientation-in-2d&quot;&gt;Orientation in 2D&lt;a class=&quot;zola-anchor&quot; href=&quot;#orientation-in-2d&quot; aria-label=&quot;Anchor link for: orientation-in-2d&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;We will start with a simple case,
orientation in two dimensions.
In two dimensions it is possible to describe the orientation of a molecules
using a single angle, $\theta$.
This is a perfectly reasonable representation.
We can find the rotational distance $\Delta\theta$ between two orientations as&lt;/p&gt;
&lt;p&gt;\[
\Delta\theta = \theta_2 - \theta_1
\]&lt;/p&gt;
&lt;p&gt;This can become a bit of an issue if we want to keep our angle bounded.
A reasonable range for an angle of rotation is $(-\pi,pi]$,
as we can assume a rotation larger occurred in the opposite direction.
The code to implement this in python would look something like the function below;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; math&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; rotationalDistance&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;theta1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; theta2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    delta_theta&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; theta2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -&lt;/span&gt;&lt;span&gt; theta1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    if&lt;/span&gt;&lt;span&gt; delta_theta&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;gt;&lt;/span&gt;&lt;span&gt; math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pi&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        delta_theta&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    elif&lt;/span&gt;&lt;span&gt; delta_theta&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; &amp;lt;=&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; -&lt;/span&gt;&lt;span&gt;math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pi&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        delta_theta&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; +=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;pi&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    return&lt;/span&gt;&lt;span&gt; delta_theta&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;complex-numbers&quot;&gt;Complex Numbers&lt;a class=&quot;zola-anchor&quot; href=&quot;#complex-numbers&quot; aria-label=&quot;Anchor link for: complex-numbers&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;An equally valid method for the representation of angle in 2D is to use complex numbers.
Any complex number $z = a+ib$ can be represented in exponential form&lt;/p&gt;
&lt;p&gt;\[
z = r\text{e}^{i\theta}
\]&lt;/p&gt;
&lt;p&gt;where $r$ is the length, or modulo, of $z$&lt;/p&gt;
&lt;p&gt;\[
r = \sqrt{a^2 + b^2}
\]&lt;/p&gt;
&lt;p&gt;and $\theta$ is the argument of $z$,
which for the first quadrant is&lt;/p&gt;
&lt;p&gt;\[
\theta = \tan^{-1}\frac{b}{a}
\]&lt;/p&gt;
&lt;p&gt;Due to the range of the $\tan^{-1}$ function only being $[-\pi/2, \pi/2]$,
the quadrant of the complex number is important to having values in the range $(-\pi,pi]$.
There is a nice computational solution to this problem implemented in almost all languages,
the &lt;code&gt;atan2&lt;/code&gt; function which works out the quadrant for us,
giving an angle in our desired range.&lt;/p&gt;
&lt;p&gt;\[
\theta = \text{atan2}(b, a)
\]&lt;/p&gt;
&lt;p&gt;Make special note of the order of arguments to the &lt;code&gt;atan2&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;Now that we understand how to convert our final complex number to a rotation,
we need to construct it from the distance between two orientations.
When we multiply two complex numbers together it can be thought of as rotating by an angle.
This can be demonstrated by performing a multiplication with
the polar form of a complex number.&lt;/p&gt;
&lt;p&gt;\[
\begin{aligned}
z &amp;amp;= r_1e^{i\theta_1} \times r_2e^{i\theta_2}\
&amp;amp;  = r_1r_2e^{i\theta_1 + i\theta_2}\
&amp;amp;  = r_1r_2e^{i(\theta_1 + \theta_2)}
\end{aligned}
\]&lt;/p&gt;
&lt;p&gt;Conversely division of a complex number by another is the distance between the two angles&lt;/p&gt;
&lt;p&gt;\[
\begin{aligned}
z &amp;amp;= r_1e^{i\theta_1} / r_2e^{i\theta_2} \
&amp;amp;= \frac{r_1}{r_2}e^{i\theta_1 - i\theta_2} \
&amp;amp;= \frac{r_1}{r_2}e^{i(\theta_1 - \theta_2)}
\end{aligned}
\]&lt;/p&gt;
&lt;p&gt;It is possible to do all our intermediate calculations using
the mathematics of complex numbers.
Then when we need an rotational distance
the complex number representing that can be converted to a angle.&lt;/p&gt;
&lt;p&gt;Implementing this is produces a function that looks like the one below,&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; math&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; complexRotation&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;initial&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; final&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    delta&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; =&lt;/span&gt;&lt;span&gt; final&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;/&lt;/span&gt;&lt;span&gt;initial&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    return&lt;/span&gt;&lt;span&gt; math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;atan2&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;delta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;imag&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; delta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;real&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the code is written in python,
most programming languages will handle complex numbers in a similar fashion.&lt;/p&gt;
&lt;h2 id=&quot;orientation-in-3d&quot;&gt;Orientation in 3D&lt;a class=&quot;zola-anchor&quot; href=&quot;#orientation-in-3d&quot; aria-label=&quot;Anchor link for: orientation-in-3d&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Dealing with the 2D case was relatively simple.
There was a single orientation to deal with,
you are probably somewhat familiar with complex numbers
(or at least were at some point),
and it is simple to draw and visualise.
In 3D things are a little more difficult.
When expressing orientation using rotations in 3D,
known as Euler angles,
there is a phenomenon known as gimbal lock.
This is where you lose a degree of freedom of expressing orientation,
similar to in spherical coordinates where if you have $\theta = 0$
then all values of $\phi$ are equivalent i.e. that degree of freedom is lost.
This means it is typically a bad idea to represent 3D orientation using angles.
Instead we use the equivalent of complex numbers, quaternions.&lt;/p&gt;
&lt;h3 id=&quot;quaternions&quot;&gt;Quaternions&lt;a class=&quot;zola-anchor&quot; href=&quot;#quaternions&quot; aria-label=&quot;Anchor link for: quaternions&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Just like complex numbers,
quaternions are a mathematical construct that allow us to represent rotations,
only in three dimensions instead of two.
As the name suggests quaternions consist of four values of the form&lt;/p&gt;
&lt;p&gt;\[
a + b\mathbf{i} + c\mathbf{j} + d\mathbf{k}
\]&lt;/p&gt;
&lt;p&gt;where the relationships&lt;/p&gt;
&lt;p&gt;\[
\mathbf{i}^2 = \mathbf{j}^2 = \mathbf{k}^2 = \mathbf{ijk} = -1
\]&lt;/p&gt;
&lt;p&gt;hold true.
These relationships are essentially giving us the rules for
the operations on the quaternions.&lt;/p&gt;
&lt;p&gt;These operations on quaternions are very similar to those on complex numbers.
The conjugate of a quaternion $q$ is;&lt;/p&gt;
&lt;p&gt;\[
q^* = a - b\mathbf{i} - c\mathbf{j} - d\mathbf{k}
\]&lt;/p&gt;
&lt;p&gt;The norm of $q$ is&lt;/p&gt;
&lt;p&gt;\[
||q|| = \sqrt{q^*q} = \sqrt{qq^*} = \sqrt{a^2 + b^2 + c^2 + d^2}
\]&lt;/p&gt;
&lt;p&gt;The reciprocal of $q$ is given as&lt;/p&gt;
&lt;p&gt;\[
q^{-1} = \frac{q^*}{||a||^2}
\]&lt;/p&gt;
&lt;p&gt;Quaternions have the same properties of multiplication and division as complex numbers.
Multiplying a quaternion by another is applying a rotation,
and the division is finding the rotation from one quaternion to the other.&lt;/p&gt;
&lt;h3 id=&quot;rotational-magnitude&quot;&gt;Rotational Magnitude&lt;a class=&quot;zola-anchor&quot; href=&quot;#rotational-magnitude&quot; aria-label=&quot;Anchor link for: rotational-magnitude&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;There are a number of methods for computing rotational magnitude using quaternions,
some of which trade of speed of calculation for accuracy.
For an overview of the different methods of computing rotational distance Du Q. Huynh&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#1&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;
does an excellent comparison of six different methods.&lt;/p&gt;
&lt;p&gt;Of the methods used to compute rotational magnitude $\Phi$,
the one I think is most suitable for use in Molecular Dynamics is&lt;/p&gt;
&lt;p&gt;[
\Phi = 2\ \text{arccos}(|\mathbf{q}_1 · \mathbf{q}_2|)
]&lt;/p&gt;
&lt;p&gt;This gives a value on the range $[0, 2\pi]$ in units of radians.
I think this is most suitable as it gives results in units of radians,
rather than some approximate distance,
while remaining simple and fast to compute.
Implemented in python this function is;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; numpy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; quaternion_distance&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;initial&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; final&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;numpy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;arccos&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;numpy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;numpy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dot&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;initial&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; final&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For an optimised computation in python over an array of values,
the below is the fastest implementation I could find.&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; numpy&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; quaternion_distance_array&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;initial&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; final&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;    return&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 2&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;*&lt;/span&gt;&lt;span&gt;numpy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;arccos&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;numpy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;numpy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;einsum&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;ij,ij-&amp;gt;i&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; initial&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; final&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more complicated operations using quaternions I would suggest having a look at &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/moble/quaternion&quot;&gt;quaternion&lt;/a&gt;,
an open source python module by Mike Boyle which adds support for quaternions to numpy.
Mike also has a &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://github.com/moble/Quaternions&quot;&gt;C++&lt;/a&gt; version of the library available.&lt;/p&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;1&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;/sup&gt;
&lt;p&gt;Huynh, D. Q. (2009). Metrics for 3D rotations: Comparison and analysis. Journal of Mathematical Imaging and Vision, 35(2), 155–164. &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;https://doi.org/10.1007/s10851-009-0161-2&quot;&gt;doi: 0.1007/s10851-009-0161-2&lt;/a&gt; (&lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://ai2-s2-pdfs.s3.amazonaws.com/5617/8de1001efe54792ad93f6980de5d5e91906b.pdf&quot;&gt;#icanhazpdf&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;
</content>
	</entry>
	<entry xml:lang="en">
		<title>Building interfaces with click</title>
		<published>2017-08-03T00:00:00+00:00</published>
		<updated>2017-08-03T00:00:00+00:00</updated>
		<link rel="alternate" type="text/html" href="https://malramsay.com/post/building-with-click/"/>
		<id>https://malramsay.com/post/building-with-click/</id>
    
		<content type="html" xml:base="https://malramsay.com/post/building-with-click/">&lt;p&gt;Running computer simulations is great.
You tell the computer what to do,
it sits there crunching numbers for a while.
Then you can come back and look at the results.
The problem with this is that most simulation packages
take the simulation parameters in an input file,
so running a simulation becomes;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write an input file&lt;/li&gt;
&lt;li&gt;Run simulation&lt;/li&gt;
&lt;li&gt;Edit input file&lt;/li&gt;
&lt;li&gt;Run simulation&lt;/li&gt;
&lt;li&gt;Forget what the old file looked like&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Click can help us break this pattern,
providing us with a simple way to pass values to scripts from the command line.
Allowing us to build command line applications that
take care of editing these input files for us.&lt;/p&gt;
&lt;h2 id=&quot;what-is-click&quot;&gt;What is Click?&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-is-click&quot; aria-label=&quot;Anchor link for: what-is-click&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Click is a python library that provides function decorators for
passing arguments to function calls through arguments and options on the command line.
Click handles all the parsing for us, including the parsing of types,
and also allows for some simple validation of values.&lt;/p&gt;
&lt;p&gt;Understanding click is probably best done through an example.
Let&#39;s say we have a script to run a simulation that looks like below&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;!/usr/bin/env python&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; run_simulation.py&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;30&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Simulation Finished!&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; __name__&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;__main__&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To change the length of time we seep for we could edit the script manually,
but we want to sleep for a whole range of different times.
To pass an sleep value from the command line we can modify the script as follows;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;!/usr/bin/env python&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; run_simulation.py&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; click&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;command&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;argument&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;sleep_time&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; type&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;int&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sleep_time&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sleep_time&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Simulation Finished!&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; __name__&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;__main__&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script can then be run as follows&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run_simulation.py&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Simulation&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Finished!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which will sleep for 1 second then print the output.&lt;/p&gt;
&lt;h2 id=&quot;taking-it-up-a-level&quot;&gt;Taking it up a level&lt;a class=&quot;zola-anchor&quot; href=&quot;#taking-it-up-a-level&quot; aria-label=&quot;Anchor link for: taking-it-up-a-level&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Changing the time we sleep for is nice,
but we are lazy and don&#39;t want to have to specify this every time.
We also want to be able to specify the output string
and the file the output is written to.
Easy!!!&lt;/p&gt;
&lt;p&gt;To make the &lt;code&gt;sleep_time&lt;/code&gt; optional we can use the following like of code&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;option&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;-t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;--sleep-time&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; type&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;int&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; default&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;now rather than passing the time as an ordered argument,
we pass it as an option as either the short &lt;code&gt;-t &amp;lt;time&amp;gt;&lt;/code&gt;
or long &lt;code&gt;--sleep-time &amp;lt;time&amp;gt;&lt;/code&gt; way.
Note that the variable name is extracted from the long form
with the hyphen being converted to an underscore.&lt;/p&gt;
&lt;p&gt;There is another improvement we can make to this &lt;code&gt;sleep_time&lt;/code&gt; option.
It is impossible to sleep for a negative time,
and we don&#39;t want to wait for longer than a minute for this to run.
Click allows us to set a range of suitable values&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;option&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;-t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;--sleep-time&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; type&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;click&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IntRange&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; max&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;60&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; default&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now when we run the simulation&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run_simulation.py&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Simulation&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Finished!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run_simulation.py&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Simulation&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Finished!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run_simulation.py&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Usage:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run_simulation.py&lt;/span&gt;&lt;span&gt; [OPTIONS&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Error:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Invalid&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; value&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; for&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;-t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;--sleep-time&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; is&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; not&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; the&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; valid&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; range&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; of&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; to&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; 60.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run_simulation.py&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 61&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Usage:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run_simulation.py&lt;/span&gt;&lt;span&gt; [OPTIONS&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Error:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; Invalid&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; value&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; for&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;-t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; /&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;--sleep-time&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 61&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; is&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; not&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; in&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; the&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; valid&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; range&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; of&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 0&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; to&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; 60.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Awesome, we can stop ourselves accidentally running a simulation that takes forever
and we get a helpful error message when we do.
In fact click will generate help documentation for us&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run_simulation.py&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-help&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Usage:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run_simulation.py&lt;/span&gt;&lt;span&gt; [OPTIONS&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Options:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;  -t,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-sleep-time&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; INTEGER&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; RANGE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;  --help&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;                          Show&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; this&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; message&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; and&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; exit.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can help it generate this help by providing a help string to our arguments and options&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;option&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;-t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;--sleep-time&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; type&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;click&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IntRange&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; max&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;60&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; default&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;              help&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Specify the time for which the simulation will run.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So we have the sleep time sorted,
now for the output string and file.
The output string is just like the sleep time,
we can add another option;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;option&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;--outstring&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; type&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; default&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Simulation Finished!&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;              help&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;String to print to output file at the end of the simulation.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just make sure that when you pass a value to this option
that spaces are either escaped or quoted,
otherwise they will be parsed as separate arguments.&lt;/p&gt;
&lt;p&gt;Click also has excellent support for file input and output,
including reading from stdin and writing to stdout.
We get this functionality by using  &lt;code&gt;click.File&lt;/code&gt; as the type like below&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;argument&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; default&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; type&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;click&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;File&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What does this all mean.
There is an argument, &lt;code&gt;output&lt;/code&gt; which has the type of a writable file.
It will return a file object that we can write to directly,
click handles the open/close for us.
The default value &lt;code&gt;&#39;-&#39;&lt;/code&gt; is stdout,
like in other command line tools where you can use &lt;code&gt;-&lt;/code&gt; to indicate
read from or write to stdin/stdout,
click has the same support.&lt;/p&gt;
&lt;h2 id=&quot;bringing-it-all-together&quot;&gt;Bringing it all together&lt;a class=&quot;zola-anchor&quot; href=&quot;#bringing-it-all-together&quot; aria-label=&quot;Anchor link for: bringing-it-all-together&quot; style=&quot;visibility: hidden;&quot;&gt;&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;With all these modifications we end up with a script that looks like this;&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;!/usr/bin/env python&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;z-l-5 z-d-3&quot;&gt; run_simulation.py&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;import&lt;/span&gt;&lt;span&gt; click&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;command&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;argument&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; default&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; type&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;click&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;File&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;option&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;-t&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;--sleep-time&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; type&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span&gt;click&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;IntRange&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; max&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;60&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; default&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;              help&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Specify the time for which the simulation will run.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;option&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;--outstring&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; type&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt; default&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Simulation Finished!&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-12&quot;&gt;              help&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;String to print to OUTPUT at the end of the simulation.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;def&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;output&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; sleep_time&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; outstring&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    time&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sleep_time&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    output&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;outstring&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; +&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; __name__&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-10&quot;&gt; ==&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;#39;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;__main__&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;#39;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    main&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To work out how to run the script we can use the help&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run_simulation.py&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-help&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Usage:&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run_simulation.py&lt;/span&gt;&lt;span&gt; [OPTIONS&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;OUTPUT&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;Options:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; -t,&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-sleep-time&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; INTEGER&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; RANGE&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;  Specify&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; the&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; time&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; for&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; which&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; the&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; simulation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;                                 will&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; --outstring&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; TEXT&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;                String&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; to&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; print&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; to&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; output&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; file&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; at&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; the&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; end&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; of&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;                                 the&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; simulation.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt; --help&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;                          Show&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; this&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; message&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; and&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; exit.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or just run the command&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-7&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; python&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; run_simulation.py&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; 1&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt; -&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-4&quot;&gt;-outstring&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;Hello World.&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-2 z-d-6&quot;&gt; hello.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have taken a simple script that can perform a single task
and with an extra 7 lines of code have turned it into a versatile tool.
There is plenty more functionality available with Click,
and you should check out the &lt;a rel=&quot;nofollow noreferrer external&quot; href=&quot;http://click.pocoo.org/5/&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
</content>
	</entry>
</feed>
