Saturday, February 29, 2020

Fun with Python Decorators on February 29

Happy leap year, everyone. Despite having been writing this blog for a decade now, it turns out I didn't think to write a post on February 29 either of the previous two leap years it's been around for. So I'm rectifying that oversight this year! Of course, there's a purpose to this post beyond merely taking the opportunity of having one up on February 29 (though that was a motivating factor). This week I wrote some rather interesting Python code, and thought I'd share it.

I was working on some code to generate synthetic data sets to test various line-fitting code, and wanted a way to add some random noise to the output, but potentially using different functions to generate the noise, which might take their own variable number of parameters. I turned to the concept of decorators in Python, which are, essentially, functions which operate on other functions. For a mathematical analogy, consider the following situation: \[f(x)=g(h(x)).\] Here we have a function, g, which operates on another function \(h(x)\), and we define this resulting function as \(f(x)\). In Python, the function g would be a decorator, as it takes another function and returns a modified form of it.

Of course, we can extend this idea further; what if, in addition to a function, g also takes additional parameters? Perhaps something like \[f(x)=g(h(x), a, b, c).\] It turns out we can do this in Python as well, though it's somewhat abstract and I don't fully understand it. I read some tutorials on the subject, I guessed at how to extend what they said into code which works, but I'd be lying if I said I truly understood it at this point. Though I'll still take a stab at explaining it. The process involves a triply-nested function to handle passing arbitrary functions and arguments to the decorator. I've embedded the entire decorator function below:

 def add_noise(noise_func, *noise_args, **noise_kwargs):  
   """Add noise from a given function to the output of the decorated function.  
   Parameters  
   ----------  
   noise_func : callable  
       A function to add noise to a data set. Should take as input a 1-D array  
       (and optionally additional parameters) and return the same.  
   args, kwargs  
       Additional arguments passed to this decorator will be passed on through  
       to `noise_func`.  
   Returns  
   -------  
   callable  
       A decorated function which adds noise from the given `noise_func` to  
       its own output.  
   """  
   def decorator_noise(func):  
       @functools.wraps(func)  
       def wrapper_noise(*args, **kwargs):  
           # Create the values from the function wrapped:  
           y = func(*args, **kwargs)  
           # Now generate noise to add to those values from the noise function  
           # provided to the decorator:  
           noise = noise_func(y, *noise_args, **noise_kwargs)  
           return y + noise  
       return wrapper_noise  
   return decorator_noise  

The main action happens in the third function where the decorated function is used to create a set of data points, y, the given noise function is used to create a variable noise, and then y and noise are added together to give the output result. You can then use this decorator on a function at definition time like so (assuming you already have a function called gaussian which takes a single parameter sigma and does the appropriate calculations:

 @add_noise(gaussian, sigma=10)  
 def generate_line_1d(x, m, b):  
   ... code ...  

This would mean any time you called the generate_line_1d function its output would be modified by the addition of Gaussian noise drawn from a distribution with a standard deviation of 10. If you instead wanted to define multiple instances of generate_line_1d with, say, different values of the sigma parameter, you could do the following:

 gaussian_10 = add_noise(gaussian, sigma=10)(generate_line_1d)  
 gaussian_20 = add_noise(gaussian, sigma=20)(generate_line_1d)  
 ...  

And so on and so forth. These various returned functions would be analogous to the \(f(x)\) defined above. You could also switch out the gaussian function for another function, which could itself take an arbitrary number of arguments.

Looking at it now, it feels less useful in the specific context I'm using it in than it seemed when I was writing it, but I'm still proud of it—it's basically more flexible and abstract than I really need, but it's still a pretty neat trick of abstraction. Come the middle of this year I'll have been using Python for a decade now, and I'm still learning new tricks and features. And hey, I might not necessarily need it now, but you never know when it might come in handy down the road! A hui hou!

Saturday, February 22, 2020

A Very Congruous Post

Over my vacation I found myself thinking about a recurrent amusement of mine: the English language contains words which have the appearance of being the negation of another word (due to the many such negatory prefixes which exist in English), and yet those oppositely-meaning words don't seem to exist. Everyone knows the opposite of “unhappy” is “happy,” but is the opposite of “disgruntled,” “gruntled?”

(P. G. Wodehouse played on this exact incongruity in his novel The Code of the Woosters, writing: “If not actually disgruntled, he was far from being gruntled.” [Also I thought I was being clever with “congruous” in the title of the post, but it turns that actually is a word.  How congruous.])

While searching on the subject, I encountered a funny poem by one J. H. Parker, apparently dating to at least 1953 (though I couldn't find out much else about it), which I consider to say much more on the subject, and better, than I could, and which I reproduce below:

A Very Descript Man .... J H Parker

I am such a dolent man,
I eptly work each day;
My acts are all becilic,
I've just ane things to say.
My nerves are strung, my hair is kempt,
I'm gusting and I'm span:
I look with dain on everyone
And am a pudent man.
I travel cognito and make
A delible impression:
I overcome a slight chalance,
With gruntled self-possession.
My dignation would be great
If I should digent be:
I trust my vagance will bring
An astrous life for me.

I must admit, I'm still trying to figure out what “span” in the second verse is meant to be the opposite of even after searching my Unix dictionary file with regular expressions, so if you have an idea please leave a comment and let me know! A hui hou!

Monday, February 10, 2020

LilyPond: LaTeX for Music

I've written in the past about \(\LaTeX\), the markup language which allows you to typeset math beautifully. This weekend I finally got around to teaching myself LilyPond, which is pretty much (as the title says) \(\LaTeX\) for music: it's a markup language which allows typesetting (or “engraving”) beautiful musical scores.

Now, I'm hardly the most musical person around; my musical education has been haphazard, much of that due, I will freely admit, to my childhood disdain for practicing things. I had a few months of violin lessens when I was about ten (right before moving internationally, which didn't help), and I taught myself to play the harmonica a bit from books in my teens (though I haven't touched it in years). I've always enjoyed listening to classical music, and I'm familiar with a lot of the terms and names for various markings, but that's about it; I can just barely read sheet music at a glacial pace.

So I was rather pleasantly surprised to discover that I took to writing LilyPond code like a fish to water after a few hours spent reading the (exceedingly well-formatted) documentation. (I think I'm just a sucker for markup languages in general; HTML, \(\LaTeX\), even SVG could be considered a form of visual markup language even if I never actually engage with the underlying code.) In less than an hour I'd managed to reproduce the short piece by Beethoven below, which, while certainly not that complex (it only runs for about 40 seconds), is also no “Twinkle, Twinkle, Little Star.” (Part of that time was that I had to look up how to handle repeats, which wasn't covered by the documentation I'd read up that point.)


I had to guess on the tempo, as the sheet music I copied it from didn't have one listed and I was going off the speed I'd heard it played. Looking up tempo names led me to the interesting fact that the pace represented by “allegretto” changed between the 18th and 19th centuries; originally it was considered to be just above andante, but it ‘moved up’ and is now considered just a bit under allegro. I realized that my conception of it has more in common with the 18th century view, and having listened to a lot of Beethoven's music (and the tempos he called allegretto) I feel like he'd probably be fine with it. I added a BPM measure to make it explicit, anyway.

All in all, I had a surprising amount of fun engraving music, and I'm itching to do more (though at this point I guess I'll mostly just be copying existing music). LilyPond is supposed to handle lyrics quite well, so it'd be interesting to try some vocal music, perhaps. I've been using a very nice editor for LilyPond called Frescobaldi, which acts as a GUI and handles a lot of the little things. It has a really nice preview feature where mousing over or selecting any note in the score has the corresponding part of the code highlighted, and vice versa. We'll see where I go with this in the future, I guess! A hui hou!