From Python to Julia & Learning by Doing: A Case Study with an Opinion Dynamics Model Simulation

For many years, I have used Python and Numpy and other packages in the same ecosystem for pretty much all of my projects. They have been my go-to for many reasons. However, a recent event with Numpy triggered my consideration to pick up another dynamically-typed language instead.

I stumbled upon Julia, as it is quite widely used in the mathematics community, and quickly discovered how nicely designed and fast it is. Coming from Python, I find Julia to be quite easy to understand. Still, there were some quirks that I had to get used to, particularly dealing with types explicitly (you don’t really need to do much of this, but this practice can speed up the code) and the way Julia handles inheritance and whatnot.

After reading the documentation and a few tutorials over a weekend, I figured the best and quickest to get myself oriented with the language is to do something with it—and preferably that something will be similar to what I would use it for in my own work. So, this post is a by-product of that. It goes over writing some simulation code for an opinion dynamics model from scratch in Julia. Rather than a full-on tutorial of how Julia works, what you will mostly find in this post is a documentation—frankly, commentation—mostly for myself of why and how I wrote the code I did, from the perspective of someone coming from Python.

Continue reading

Python Generator Expressions

A list comprehension in Python is probably one of the first things a Python coder will learn (usually right after arithmetic and boolean logic). But in my own experience, not a lot of people talk about a generator expression. While I have been coding in Python for over 5 years, I personally have learned about it not so long ago.

Instead of using a list comprehension to get something like

[i for i in range(10) if i % 2 == 0]

we can do something like,

(i for i in range(10) if i % 2 == 0)

The latter line of code is a generator expression and returns a generator object which is stored in memory. The generator then only generates the values only when called, so we don’t have to allocate a lot of memory to store something in a list. Compared to a list, a generator takes up much less space. Moreover, it is much more efficient (faster) than building a list.

The above line of code is equivalent to writing a generator function:

def gen(bound):
    for i in range(bound):
        if i % 2 == 0:
            yield i

Some simple examples of what you can use generator expressions for:

sum(1 for i in some_list if condition)  # sum conditional elements in a list
for i in irange(1000000): do_something(i)
# irange() is a range generator and is more efficient than range() which builds the whole list

For further examples and explanations, see