The past is powerful evidence for arguments about the present. Since the time of Livy and Tacitus, it’s been common to cite history to advance some ideological, cultural, or political idea. While there’s nothing wrong with this in principle, these lines of discourse often break down because (1) people know very little history and (2) what little history they do know is usually wrong.
Popular understanding of history is shaped mostly by popular culture: books, movies, and video games. These sources aren’t intrinsically bad—I’ll never remember early modern European states as well as my friend who played hundreds of hours of Europa Universalis IV in high school. But the overall effect of a history education dominated by Gladiator, 300, and Ben-Hur is to give people a weird and distorted view of the past,1 and one inchoate enough to be shaped into almost any argument about the present.
If we want to build up a more accurate view of what it was like to live in the past, we want to start with the most fundamental questions: how did people live? How did they spend their time? What did they do for work and for leisure? What did they worry about? Unfortunately, these questions are almost always unanswerable. Most historical documents don’t touch on the daily life of average people, focusing instead on chronicling noble deeds, recording economic transactions, and so on.
But exceptions can be found. The village of Montaillou is a small, mountainous village of about 250 inhabitants in the French Pyrenees (in Occitania, what was then the Duchy of Foix and is today the department of Ariège). Montaillou was remote and unexceptional in almost every way, making it exactly the sort of place which we’d never expect to see in historical documents—except that by 1300 it was one of the last strongholds of Catharism, a Christian heresy which had been almost completely eliminated by the Albigensian Crusade in the 1210s.
This is typical of historical mountain societies. In his landmark work The Mediterranean and the Mediterranean World in the Age of Philip II, Fernand Braudel writes (pp. 38–39):
There can be no doubt that the lowland, urban civilization penetrated to the highland world very imperfectly and at a very slow rate… for the simple reason that mountains are mountains: that is, primarily an obstacle, and therefore also a refuge, a land of the free. For there men can live out of reach of the pressures and tyrannies of civilization: its social and political order, its monetary economy.
The Cathars who had come to the mountains to flee persecution were dualist Gnostics who believed in the reincarnation of the soul. Their clerics and leaders, called goodmen (bonhommes) or parfaits, were celibate and refused to eat meat or drink wine. Ordinary Cathar followers didn’t hold themselves to this standard until their deathbed, when they would convert to Catharism in a ritual called the consolamentum and then completely fast until dying of hunger (called the endura). Cathars rejected the sacraments, mocked the priests and rituals of the Church, and saw themselves as the true worshipers of the Christian God.
Following the Albigensian Crusade, Montaillou and other rural towns became a refuge for Catharism. This eventually drew the attention of the French Inquisition in the person of Jacques Fournier, bishop of Pamiers. In 1320, Fornier arrested a large percentage of the population of Montaillou and interrogated them at length, recording “substantial and very detailed evidence” (xiv) about their lives. During his tenure at Pamiers, Fournier’s inquisition court investigated 578 people over 370 different days, with scribes and notaries keeping a detailed record of all proceedings in what is now known as the Fournier Register. This extraordinary document might have been lost to history except for the fact that Fournier became Pope Benedict XII in 1334 and the Fournier Register was brought to the Vatican Library.
The Fournier Register was revisited by Annales historian Emmanuel Le Roy Ladurie. His 1975 work Montaillou, village occitan de 1294 à 1324 uses the details contained in the Register to reconstruct the world of Montaillou: who the people were, what they thought about, and how they lived. I originally read this book right before starting college and liked it a lot. Recently, I’ve found myself thinking back to Montaillou in everyday discussions about history and decided to read Le Roy Ladurie’s book again.
In this post, I hope to give a taste of the world of Montaillou, and how surprisingly normal (or abnormal) aspects of this world can seem to modern sensibilities. I’m focusing on the questions that interested me the most in this reading, and have omitted a lot of interesting characters and life histories for the sake of space and focus. If you find this review interesting, you should certainly pick up the book yourself—I’ve indicated page numbers in the 30th anniversary edition for easy reference.
Montaillou was (and is) a small village on the French side of the Pyrenees, at an elevation of approximately 4500 feet. In a physical and economic sense, Montaillou was incredibly isolated. There was essentially no non-foot traffic in or out of the village, so goods were carried by hand or with a mule. Montaillou had no blacksmith or tailor, and iron tools were rare (7). Montaillou was too small to have its own mill, so villagers would take wheat by mule to the larger town of Ax-les-Thermes, grind it at the mill, and return with flour, about 10 miles by Google Maps (9).
Here's a picture of modern Montaillou from Wikipedia. The ruins of medieval Montaillou are visible at the top of the hill, and the snow-capped Pyrenees are visible some 20 miles to the south.
The bulk of calories came from bread (made from wheat or millet), and cheese was the primary protein source (8–9). Other documented animal foods include mutton, bacon, goat’s liver, eggs, and trout (9, 82, 83, 124). Cabbages, leeks, broad beans, and turnips were the most common vegetables (9). Most people in Montaillou farmed and raised animals: pigs, cows, sheep, chickens, geese, and so on. Almost everyone kept sheep, but there were some skilled itinerant “professional” shepherds who travelled across the Pyrenees living in the mountains and supervising large herds (69–135), stopping back home from season to season.
Apart from the coinage that they used, the people of Montaillou were not “French” in any meaningful sense. They spoke a dialect of Occitanian that was distinct to their region, “about a thousand people at the most” (286). When forced to flee religious persecution, villagers went not to other regions of France but to Catalonia, Lombardy, Sicily, or Valencia (286). They almost always married within their village; in the cases where someone from Montaillou married someone from the outside, it was almost always from a neighboring village (183). Thus the world of Montaillou was, in a personal sense, very small indeed.
The house (domus in Latin, ostal in Occitan) was the fundamental physical and social unit of Montaillou. Physically, the kitchen was the central room of the house, and perhaps the only one built of stone (39). The hearth and cooking utensils were in the middle of the kitchen, hams hung from the roof, and a table and chairs were off to the side (37–38). A cellar was often adjacent to the kitchen (38).
Personal space was not as scarce as people sometimes imagine in medieval times. Most rooms had only one or two people, and people had separate beds (38–39). Children and adults slept in separate rooms (39). Most houses only had one story, but richer villagers might have two-story houses (39). Animals typically slept in the house at night, albeit in separate rooms, and used the same door as people; sick people were sometimes put near animals to keep them warm at night. Only relatively wealthy farms had separate stables, pigsties, and sheep-pens (40).
The intellectual and social life of Montaillou revolved around the domus, “a unifying concept in social, family, and cultural life” (25). When villagers discussed Catharism and Catholicism, they identified beliefs not with individuals but with houses (28). To be the head of the house was a significant position of authority; to have one’s house confiscated or destroyed was cataclysmic (35–37).
It’s become somewhat popular in recent years to argue against the primacy of the nuclear family. In his 2020 article “The Nuclear Family Was a Mistake”, David Brooks argues that “big, interconnected, and extended families” are the historical norm and a healthier & more natural way to live. I’ve thought about this essay and argued about it with friends many times over the past few years; in fact, these arguments were a large part of why I originally wanted to reread Montaillou.
I expected the history of Montaillou to support Brooks’s position that extended families were more normal than nuclear families in medieval societies, but it didn’t. The vast majority of houses held nuclear families, or nuclear-ish families where an uncle or grandmother lived with the core family unit. Several examples of more extended families are documented, but they are “very rare cases” and usually unstable (48). As a rule, there was only one married couple per house and the house organized itself around this couple.
Montaillou was largely patriarchal, but not entirely so. There were maternal houses where the sons took their mother’s name and son-in-laws took their wife’s name (34). Nor was primogeniture absolute. Fathers generally determined who inherited the house, but the inheritor did not have to be the firstborn, and the other sons would receive a smaller portion called a fratrisia (36). Both these facts surprised me.
Marriage was typically arranged by the family and “involved much more than a mere agreement between two individuals” (180), with numerous relatives often involved. Dowries were substantial enough that families worried that marrying their daughters might bring economic ruin upon their houses, but remained the distinct property of the wife after marriage. If the husband died first, the widow retained her dowry separately from whomever might inherit the rest of the possessions (35–36).
Widows were common because women were typically married young, between the ages of 15 and 20, while men typically waited until after 25 to marry (190). Le Roy Ladurie writes (191):
Husbands in Montaillou were generally fully adult and they often married young innocents. The girls were beginners; the men were settling down. This difference in age in a world where people died young soon produced a crop of young widows. With one husband in the grave, women prepared to go through one or even two more marriages.
While marriage for love was not the primary objective, neither was it impossible: “it was possible to love passionately within apparently rigid structures which predisposed towards and presided over the choice of a marriage partner” (187). That being said, the sources rarely speak of women’s feelings towards their husbands (189):
It is probably, and sometimes provable, that the young men in love whom we find in the Register aroused similar feelings in the girls they married. But references are scarce. Rightly or wrongly, in upper Ariège the man was supposed to possess the initiative or even the monopoly in matters of love and affection, at least in the realm of courtship and marriage.
As might be expected, families in Montaillou were considerably larger than today. Based on data in the Register, Le Roy Ladurie estimates that there were 4.5 legitimate births per family, plus a small but non-negligible number of illegitimate births (204). For all but the wealthiest of families this was an asset: “a domus rich in children was a domus rich in manpower; in other words, rich, pure and simple” (207). Contraception was practiced, especially outside marriage, but not abortion (172–173, 209). Children were nursed for a long time, perhaps until two years old (208).
Modern people sometimes allege that love for young children is a modern phenomenon, citing the Roman paterfamilias as evidence to the contrary. In Montaillou, as today, men and women loved their young children, laughing & playing games with them and weeping bitterly when they died (210–213). The mortality rate for children and adolescents is not clear from our data but “was probably high” (221). Schooling was practical, not formal—children worked with their parents, outside and inside, and were taught religion (Catholic or Cathar) by their families. Children were often put to bed early; the Register records that a six-year-old girl is put to bed before dinner is served to guests (215).
At the age of 12 or so, boys changed status. The word used to describe them shifts from puer (used from age 2 onwards) to adulescens or juvenis. As adolescents, they began to work as apprentice shepherds, were considered to have reached the age of reason, and could be arrested for heresy (215–216). At 18, men became full-fledged adults (216). I’ll quote Le Roy Ladurie directly on aging (216):
When it came to old age, there was a different pattern for men and women. In their thirties, men were in their prime. In their forties, they were still strong. But after about fifty a man was old in those days, and his prestige, unlike that of an elderly woman, did not increase with time.
Domestic servants and hired shepherds were common, and servants often lived in their employers’ houses (115). Labor markets seem quite liquid in this time period—shepherds are often hired for a season and “did not feel this instability as some kind of oppression or alienation” (114). People were part of a market economy, but the 1300s had “easy norms” (124):
Everyone who has studied the daily life of the people of Montaillou, whether locals or emigrants, has been struck by the relaxed rhythm of their work, whether they were shepherds, farmers, or artisans… When necessary [a shepherd] got his friends to look after his sheep for him while he went down to the neighbouring town, to take, or to collect, money. Or he might absent himself for purely personal reasons, without any problems of time-keeping or supervision, to go and visit friends, mistresses (unless they came up directly to see him in his cabane) or fellow-sponsors, friends acquired at baptisms recently or long ago…. [He] enjoyed parties and entertainment, and even just a good meal among friends.
The social divide between nobles and non-nobles in Montaillou was not vast. Le Roy Ladurie writes that “ladies and châtelaines, when they met with peasant women, did not hesitate to settle down for a gossip; they might even kiss and embrace” (16). This was likely less true in larger towns or cities; “the absence of strong demarcation between groups can be explained by the relative poverty of the mountain nobility” as contrasted to “the nobles of Paris or Bordeaux, with their huge manorial estates and their vineyards worth their weight in gold” (17).
People had many close, intimate friendships outside the immediate or extended family. Groups of women socialized while fetching water, at the mill, in the kitchen, or sitting in the sun in the village square (251–254). Men met to sing, play chess, or speculate about if Heaven would run out of space for the souls of the dead (259–260). Sunday Mass was the central social event of the week, even for heretics or non-believers, but even so only about half the populace went to Mass any given week (265; 305).
Even in a village of a few hundred people, it was possible to keep secrets. Heretic parfaits snuck from house to house via secret passages (41) or disguised themselves as woodcutters to move about incognito (75–76), while nosy neighbors peeked through holes in doors or lifted up roofs (which must have been flimsy) to spy on heretical conversations (245; 256).
The taxes owed to the nobility were relatively light, particularly compared to the heavy taxes extracted by the Church, and the latter were hated much more than the former (20–23). It was common for people who owed the Church money (including tithes) to be excommunicated (335). The success of Catharism in Montaillou can be largely attributed to the burdensome taxation of the Church, which gave rise to strong anti-clerical feelings far in advance of any theological rationale. I was surprised to learn that indulgences were a part of Catholic religious practice even in the early 1300s, and were hated then too (334).
Many people envision medieval Europe as a theocracy where Catholic morals reigned supreme, either for good or for ill. At least in the case of Montaillou, this wasn’t true—there were lots of mistresses, concubines, prostitution, illegitimate children, and sordid love affairs (45, 151, 169). Homosexuality is not recorded in Montaillou but is documented in the larger cities of Pamiers and Toulouse (144–149). Approximately 10% of couples in Montaillou during this time period were illicit or “living in sin,” and non-marital cohabitation was common enough that a visitor to one house was uncertain if the woman there was the man’s wife or his concubine. Le Roy Ladurie writes (169):
If anyone came across a couple openly living together, the reaction was much the same as it would be today. Were they legally married or not?
Sexual ethics aside,2 crime was rare. While petty theft was not uncommon and grazing rights were always a source of conflict, Montaillou was an intimate society where “everyone knew everyone else and strangers were easy to find,” making crimes against property rather impractical (329). In cases in which a flock or house was confiscated, it was always under some legal mechanism rather than outright use of force. During the decades covered by the Fournier Register, a single murder and a handful of rapes are recorded—while this significantly exceeds the present on a per capita basis, these events were rare and shocking to the villagers.
The Fournier Register excels as a window into peasant culture in the 14th century. Peasants were “fond of abstract thought and even of philosophy and metaphysics” (232), and Le Roy Ladurie remarks on “the lack of social distance between the countryman… and the nobleman, the priest, the merchant, and the master craftsman, in a world where manual labour, especially craftsmanship, was not despised” (232). The primary social engagement was the evening meal, where groups of peasants would sit for hours at benches around the fire remembering village history, discussing the health of people and animals, arguing about the resurrection of the body, or simply gossiping (247, 250). Wine was served, but not to excess—drunkenness is only mentioned in urban contexts in the Fournier Register, and even there rarely (249).
Books, while rare and expensive, were important and recognized cultural objects—both Cathar parfaits and Catholic priests derived intellectual legitimacy from the possession of books (211, 234–236). It was rare, but not unheard of, for laymen to be able to read: Le Roy Ladurie estimates that four out of the roughly 250 inhabitants of Montaillou were literate (239). As a result most ideas were transmitted orally, and the Cathar parfaits were renowned for their oration and eloquence.
I was very surprised to learn that history was virtually unknown in Montaillou. Only in larger cities like Pamiers was Roman antiquity known and discussed, and there only rarely; in Montaillou, history “scarcely went back further than the previous Comte de Foix” (282). The Church filled this void, but imperfectly. Villagers knew almost nothing of Christian history besides Creation, the lives of Mary, Jesus, and the Apostles, and the coming Day of Judgement and the Resurrection (281). As Le Roy Ladurie describes it, “the people of Montaillou lived in a kind of ‘island in time,’ even more cut off from the past than from the future” (282).
I’ve only scratched the surface of Montaillou here. I haven’t told the story of Pierre Clergue, heretic village priest who had at least nine mistresses (and probably more) and used his brutal authority to crush village rivals; Pierre Maury, itinerant master shepherd with a love of poverty and a fatalist outlook on life; or Béatrice de Planissoles, twice-widowed noblewoman with a proclivity for dramatic love affairs with non-nobles between husbands. I feel some guilt in omitting these thrilling tales from my review, but I don’t think I can do them justice here.
What I’ve instead attempted to do here is give the flavor of medieval life as recounted by Le Roy Ladurie. Since this is a microhistory, we have to be cautious about how much we can generalize; Montaillou was different in the 14th century than in the 10th century, and would be different again by the 17th century, to say nothing of how life would be different in Frisia, Andalusia, Calabria, or outside medieval Europe. (If there’s one thing we can learn from Montaillou, it’s that history is big and strange.)
Still, I updated a number of my beliefs about the past after reading about Montaillou. Here’s a few common claims that I thought Montaillou directly addressed.
I really enjoyed this microhistory and would love to read different accounts of everyday life in medieval Europe—if you have any recommendations, please let me know!
The past few years of “AI for life science” has been all about the models: AlphaFold 3, neural-network potentials, protein language models, binder generation, docking, co-folding, ADME/tox prediction, and so on. But Chai-2 (and lots of related work) shows us that the vibes are shifting. Models themselves are becoming just a building block; the real breakthroughs are going to happen at the workflow level, as we learn how to combine these models into robust and performant pipelines.
Workflows are the new models. To have a state-of-the-art computational stack for drug discovery (or protein engineering, or materials design, or anything else), it’s no longer enough to have just a single state-of-the-art model. You need a suite of modular tools that you can combine in a way that makes sense for your task. (At Rowan, we’re seeing this happen all over the industry.)
What does this mean in practice? Here are two imaginary case studies illustrating what modern computational chemistry looks like in 2025:
A company is developing a new inorganic photocatalyst for bulk acid–alkene coupling (following Zhu and Nocera, 2020). Their workflow might look something like this:
The entire cycle can be repeated ad nauseum to generate new candidates, with the focus gradually shifting from exploration to exploitation.
A company has identified new CNS biological targets that they hope to inhibit with a small molecule. Their workflow might look something like this:
This cycle, too, can be repeated until you run out of Modal credits a set of promising candidates is identified for synthesis.
Neither of these case studies is based on a particular company; instead, they’re meant to illustrate the sort of ML-native workflows we’re seeing from early adopters across the chemical sciences. For simplicity, experimental integration isn’t shown here, but any sane scientist will obviously incorporate wet-lab testing as soon as possible and feed those insights back into the top of the funnel.
In any case, the overall point is clear—no single model can by itself solve every problem, and figuring out the right way to combine a set of models is itself a non-trivial system-design problem. It’s entirely possible to create a state-of-the-art workflow simply by combining “commoditized” open-source models in a new way, and so far the resultant workflows don’t seem obvious or easy to copy. This defies popular intuition about what constitutes a “moat” for AI companies.
More metaphysically, the line between workflows and models is blurring. Many ML-adjacent people think of models as the active unit of science: “they have a model for X” or “we’re building a model for Y.” But, as shown above, most state-of-the-art research today requires lots of individual ML models, and many “models” are already miniature workflows. For instance, running a single inference call through the Uni-pKa “model” requires enumerating all possible microstates, performing a conformer search, and running geometry optimizations on every individual conformer—just to generate the pairwise-distance matrix used as input for the actual ML model.
Why does this matter? Here are a few thoughts that I've had, after thinking about this point:
“You dropped a hundred and fifty grand on a f***** education you coulda' got for a dollar fifty in late charges at the public library.” —Good Will Hunting
I was a user of computational chemistry for years, but one with relatively little understanding of how things actually worked below the input-file level. As I became more interested in computation in graduate school, I realized that I needed to understand everything much more deeply if I hoped to do interesting or original work in this space. From about August 2021 to December 2023, I tried to learn as much about how computational chemistry worked as I could, with the ultimate goal of being able to recreate my entire computational stack from scratch.
I found this task pretty challenging. Non-scientists don’t appreciate just how esoteric and inaccessible scientific knowledge can be: Wikipedia is laughably bad for most areas of chemistry, and most scientific knowledge isn’t listed on Google at all but trapped in bits and pieces behind journal paywalls. My goal in this post is to describe my journey and index some of the most relevant information I’ve found, in the hopes that anyone else hoping to learn about these topics has an easier time than I did.
This post is a directory, not a tutorial. I’m not the right person to explain quantum chemistry from scratch. Many wiser folks than I have already written good explanations, and my hope is simply to make it easier for people to find the “key references” in a given area. This post also reflects my own biases; it’s focused on molecular density-functional theory and neglects post-Hartree–Fock methods, most semiempirical methods, and periodic systems. If this upsets you, consider creating a companion post that fills the lacunæ—I would love to read it!
I started my journey to understand computational chemistry with roughly three years of practice using computational chemistry. This meant that I already knew how to use various programs (Gaussian, ORCA, xTB, etc), I was actively using these tools in research projects, and I had a vague sense of how everything worked from reading a few textbooks and papers. If you don’t have any exposure to computational chemistry at all—if the acronyms “B3LYP”, “def2-TZVPP”, or “CCSD(T)” mean nothing to you—then this post probably won’t make very much sense.
Fortunately, it’s not too hard to acquire the requisite context. For an introduction to quantum chemistry, a good resource is Chris Cramer's video series from his class at UMN. This is pretty basic but covers the main stuff; it's common for one of the classes in the physical chemistry sequence to also cover some of these topics. If you prefer books, Cramer and Jensen have textbooks that go slightly more in depth.
For further reading:
In general, computational chemistry is a fast-moving field relative to most branches of chemistry, so textbooks will be much less valuable than e.g. organic chemistry. There are lots of good review articles like this one which you can use to keep up-to-date with what's actually happening in the field recently, and reading the literature never goes out of style.
All of the above discuss how to learn the theory behind calculations—to actually get experience running some of these, I'd suggest trying to reproduce a reaction that you've seen modeled in the literature. Eugene Kwan's notes are solid (if Harvard-specific), and there are lots of free open-source programs that you can run like Psi4, PySCF, and xTB. (If you want to use Rowan, we've got a variety of tutorials too.) It's usually easiest to learn to do something by trying to solve a specific problem that you're interested in—I started out trying to model the effect of different ligands on the barrier to Pd-catalyzed C–F reductive elimination, which was tough but rewarding.
Molecular dynamics is important but won’t be covered further here. The best scientific introduction to MD I've come across is these notes from M. Scott Shell. If you want to go deeper, I really liked Frenkel/Smit; any knowledge of statistical mechanics will be very useful too, although I don’t have any recommendations for stat-mech textbooks. To get started actually running MD, I'd suggest looking into the OpenFF/OpenMM ecosystem, which is free, open-source, and moderately well-documented by scientific standards. (I've posted some intro scripts here.)
Cheminformatics is a somewhat vaguely defined field, basically just "data science for chemistry," and it's both important and not super well documented. If you're interested in these topics, I recommend just reading Pat Walters' blog or Greg Landrum's blog: these are probably the two best cheminformatics resources.
As with all things computational, it's also worth taking time to build up a knowledge of computer science and programming—knowing how to code is an investment which will almost always pay itself back, no matter the field. That doesn't really fit into this guide, but being good at Python/Numpy/data science/scripting is worth pursuing in parallel, if you don't already have these skills.
I started my computational journey by trying to write my own quantum chemistry code from scratch. My disposition is closer to “tinkerer” than “theorist,” so I found it helpful to start tinkering with algorithms as quickly as possible to give myself context for the papers I was trying to read. There are several toy implementations of Hartree–Fock theory out there that are easy enough to read and reimplement yourself:
Based on these programs, I wrote my own Numpy-based implementation of Hartree–Fock theory, and got it to match values from the NIST CCCBDB database.
iteration 05: ∆P 0.000001 E_elec -4.201085 ∆E -0.000000 SCF done (5 iterations): -2.83122 Hartree orbital energies: [-2.30637605 -0.03929435] orbital matrix: [[-0.71874445 0.86025802] [-0.44259188 -1.02992712]] density matrix: [[1.03318718 0.63622091] [0.63622091 0.39177514]] Mulliken charges: [[1.32070648 0.81327091] [1.10313469 0.67929352]]
I highly recommend this exercise: it makes quantum chemistry much less mysterious, but also illustrates just how slow things can be when done naïvely. For instance, my first program took 49 minutes just to compute the HF/6-31G(d) energy of water, while most quantum chemistry codes can perform that calculation in under a second. Trying to understand this discrepancy was a powerful motivation to keep reading papers and learning new concepts.
Armed with a basic understanding of what goes into running a calculation, I next tried to understand how “real” computational chemists structure their software. The best single paper on this topic, in my opinion, is the “direct SCF” Almlof/Faegri/Korsell paper from 1982. It outlines the basic method by which almost all calculations are run today, and it’s pretty easy to read. (See also this 1989 followup by Haser and Ahlrichs, and this 2000 retrospective by Head-Gordon.)
I found several other papers very useful for building high-level intuition. This overview of self-consistent field theory by Peter Gill is very nice, and Pople’s 1979 paper on analytical first and second derivatives within Hartree–Fock theory is a must-read. This 1995 Pulay review is also very good.
Using the intuition in these papers, I was able to outline and build a simple object-oriented Hartree–Fock program in Python. My code (which I called hfpy, in allusion to the reagent) was horribly inefficient, but it was clean and had the structure of a real quantum chemistry program (unlike the toy Jupyter Notebooks above). Here’s some of the code that builds the Fock matrix, for instance:
for classname in quartet_classes.keys(): num_in_class = len(quartet_classes[classname]) current_idx = 0 while current_idx < num_in_class: batch = ShellQuartetBatch(quartet_classes[classname][current_idx:current_idx+Nbatch]) # compute ERI # returns a matrix of shape A.Nbasis x B.Nbasis x C.Nbasis x D.Nbasis x NBatch ERIabcd = eri_MMD(batch) shell_quartets_computed += batch.size for i, ABCD in enumerate(batch.quartets): G = apply_ERI(G, *ABCD.indices, bf_idxs, ERIabcd[:,:,:,:,i], ABCD.degeneracy(), dP) current_idx += Nbatch # symmetrize final matrix # otherwise you mess up, because we've added (10|00) and not (01|00) (e.g.) G = (G + G.T) / 2 if incremental: molecule.F += G else: molecule.F = molecule.Hcore + G print(f"{shell_quartets_computed}/{shell_quartets_possible} shell quartets computed")
Any quantum-chemistry developer will cringe at the thought of passing 5-dimensional ERI arrays around in Python—but from a pedagogical perspective, it’s a good strategy.
Armed with a decent roadmap of what I would need to build a decent quantum chemistry program, I next tried to understand each component in depth. These topics are organized roughly in the order I studied them, but they’re loosely coupled and can probably be addressed in any order.
As I read about various algorithms, I implemented them in my code to see what the performance impact would be. (By this time, I had rewritten everything in C++, which made things significantly faster.) Here’s a snapshot of what my life looked like back then:
benzene / 6-31G(d) 12.05.22 - 574.1 s - SHARK algorithm implemented 12.20.22 - 507.7 s - misc optimization 12.21.22 - 376.5 s - downward recursion for [0](m) 12.22.22 - 335.9 s - Taylor series approx. for Boys function 12.23.22 - 300.7 s - move matrix construction out of inner loops 12.25.22 - 267.4 s - refactor electron transfer relation a bit 12.26.22 - 200.7 s - create electron transfer engine, add some references 12.27.22 - 107.9 s - cache electron transfer engine creation, shared_ptr for shell pairs 12.28.22 - 71.8 s - better memory management, refactor REngine 12.29.22 - 67.6 s - more memory tweaks 01.03.23 - 65.1 s - misc minor changes 01.05.23 - 56.6 s - create PrimitivePair object 01.07.23 - 54.7 s - Clementi-style s-shell pruning 01.08.23 - 51.7 s - reduce unnecessary basis set work upon startup 01.10.23 - 49.9 s - change convergence criteria 01.11.23 - 61.1 s - stricter criteria, dynamic integral cutoffs, periodic Fock rebuilds 01.12.23 - 42.5 s - improved DIIS amidst refactoring 01.20.23 - 44.7 s - implement SAD guess, poorly 01.23.23 - 42.6 s - Ochsenfeld's CSAM integral screening 01.30.23 - 41.7 s - improve adjoined basis set generally 02.08.23 - 38.2 s - introduce spherical coordinates. energy a little changed, to -230.689906932 Eh. 02.09.23 - 12.3 s - save ERI values in memory. refactor maxDensityMatrix. 02.15.23 - 9.0 s - rational function approx. for Boys function 02.18.23 - 8.7 s - minor memory changes, don't recreate working matrices in SharkEngine each time
Here's a rough list of the topics I studied, with the references that I found to be most helpful in each area.
Writing your own QM code illustrates (in grisly fashion) just how expensive computing electron–electron repulsion integrals (ERIs) can be. This problem dominated the field’s attention for a long time. In the memorable words of Jun Zhang:
Two electron repulsion integral. It is quantum chemists’ nightmare and has haunted in quantum chemistry since its birth.
To get started, Edward Valeev has written a good overview of the theory behind ERI computation. For a broad overview of lots of different ERI-related ideas, my favorite papers are these two accounts by Peter Gill and Frank Neese. (I’ve probably read parts of Gill’s review almost a hundred times.)
Here are some other good ERI-related papers, in rough chronological order:
Writing your own QM code also illustrates how important SCF convergence can be. With the most naïve approaches, even 15–20 atom molecules often struggle to converge—and managing SCF convergence is still relatively unsolved in lots of areas of chemistry today, like radicals or transition metals.
This is obviously a big topic, but here are a few useful references:
Density-fitting/resolution-of-the-identity (“RI”) approaches are becoming de rigueur for most applications. This 1995 Ahlrichs paper explains “RI-J”, or density fitting used to construct the J matrix (this 1993 paper is also highly relevant.) This 2002 Weigend paper extends RI-based methods to K-matrix construction. Sherrill’s notes are useful here, as is the Psi4Numpy tutorial.
RI methods require the use of auxiliary basis sets, which can be cumbersome to deal with. This 2016 paper describes automatic auxiliary basis set generation, and this 2023 paper proposes some improvements. There’s now a way to automatically create auxiliary basis sets through the Basis Set Exchange API.
For Hartree–Fock theory, there are a lot of nice example programs (linked above); for density-functional theory, there are fewer examples. Here are a few resources which I found to be useful:
For large systems, it’s possible to find pretty large speedups and reach a linear-scaling regime for DFT (excepting operations like matrix diagonalization, which are usually pretty fast anyway). This 1994 paper discusses extending the fast multipole method to Gaussian distributions, and how this can lead to linear-scaling J-matrix construction, and this 1996 paper discusses a similar approach. This other 1996 paper describes a related near-linear-scaling approach for K matrices. There are a bunch more papers on various approaches to linear scaling (Barnes–Hut, CFMM, GvfMM, Turbomole’s RI/CFMM, etc), but I think there are diminishing marginal returns in reading all of them.
Geometry optimization for molecular systems is pretty complicated. Here’s a sampling of different papers, with the caveat that this doesn’t come close to covering everything:
I think the best guides here are the Gaussian vibrational analysis white paper and the Gaussian thermochemistry white paper—they basically walk through everything that’s needed to understand and implement these topics. It’s now pretty well-known that small vibrational frequencies can lead to thermochemistry errors; Rowan has a blog post that discusses this and similar errors.
Implicit solvent models, while flawed, are still essential for a lot of applications. This 1993 paper describes the COSMO solvation method, upon which most modern implicit solvent methods are built. Karplus and York found a better way to formulate these methods in 1999, which makes the potential-energy surface much less jagged:
While there are many more papers that one could read in this field, these are the two that I found to be most insightful.
I haven’t talked much about basis sets specifically, since most basis-set considerations are interwoven with the ERI discussion above. But a few topics warrant special mention:
There are hundreds of other papers which could be cited on these topics, to say nothing of the myriad topics I haven’t even mentioned here, but I think there’s diminishing marginal utility in additional links. The knowledge contained in the above papers, plus ancillary other resources, were enough to let me write my own quantum-chemistry code. Over the course of learning all this content, I wrote four different QM programs, the last of which, “Kestrel,” ended up powering Rowan for the first few months after we launched.
These programs are no longer directly relevant to anything we do at Rowan. We retired Kestrel last summer, and now exclusively use external quantum-chemistry software to power our users’ calculations (although I’ve retained some strong opinions; we explicitly override Psi4’s defaults to use the Stratmann–Scuseria–Frisch quadrature scheme, for instance).
But the years I spent working on this project were, I think, an important component of my scientific journey. Rene Girard says that true innovation requires a “mastery” of the past’s achievements. While I’m far from mastering quantum chemistry, I think that I’d be ill-suited to understand the impact of recent advances in neural network potentials without having immersed myself in DFT and conventional physics-based simulation for a few years.
And, on a more personal note, learning all this material was deeply intellectually satisfying and a fantastic use of a few years. I hope this post conveys some of that joy and can be helpful for anyone who wants to embark on a similar adventure. If this guide helped you and you have thoughts on how I could make this better, please feel free to reach out!
Thanks to Peter Gill, Todd Martínez, Jonathon Vandezande, Troy Van Voorhis, Joonho Lee, and Ari Wagen for helpful discussions.
Recently, Kelsey Piper shared that o3 (at time of writing, one of the latest reasoning models from OpenAI) could guess where outdoor images were taken with almost perfect accuracy. Scott Alexander and others have since verified this claim.
I’ve been playing around with this too: with the prompt linked in Scott’s post, o3 can guess where my photos were taken almost every time, even when I intentionally avoid anything that looks like it might be too helpful. After inspecting the reasoning, I was surprised to learn that o3 can almost always estimate the latitude to within a few degrees, which vastly restricts the range of potential answers.
I didn’t think this was possible before doing the research for this post. Here’s three recent examples—see if you can estimate the latitude yourself. (You may want to open the images in a new tab to zoom in.)
o3 guessed that these were 40–45º N, 25–28 ºN, and 34–36º S; in every case the answer was within that range. (I make sure to only give o3 screenshots, so it can’t access EXIF data or otherwise cheat at this assessment.)
How is this possible? Here’s my best understanding of what o3 is doing, informed by a bunch of back-and-forth conversations with a friendly neighborhood AI model. (I’ll be assuming that the photo also has compass information, in keeping with standard GeoGuessr rules.)
Let’s make this as simple as possible to start. On the spring equinox, at noon, at the equator, the sun is directly overhead. As a result, tall objects without any overhang won’t cast any shadows.
If you’re not on the equator, then objects will still cast a shadow, and the length of the shadow S relative to the object’s height H tells you what latitude you’re at. Formally, we can define the solar elevation θ := arctan(H/S), and approximate the latitude φ as 90º − θ.
I don’t do much mental trigonometry these days, but it’s pretty easy to make a table with some key values:
S/H | θ (Solar Elevation) | φ (Latitude) |
---|---|---|
2:1 | 26º | 64º |
1.3:1 | 37º | 53º |
1:1 | 45º | 45º |
0.7:1 | 55º | 35º |
0.5:1 | 63º | 27º |
0 (no shadow) | 90º | 0º |
With a compass, figuring out which hemisphere you’re in is easy. In the northern hemisphere, the sun is south of you, so the shadows point north; in the southern hemisphere, the shadows point south.
Unfortunately, it’s more complex than this—we’re not always on the equinox, meaning that we also have to account for solar declination (δ). The solar declination reflects how far away from the equator the sun is on any given date; δ is +23.4º on the summer solstice and -23.4º on the winter solstice. We have to adjust the above formula to take this into account: now φ ≈ 90º − θ + δ.
Qualitatively, this means that shadows are shorter in summer than winter. A H/S ratio of 1:1 implies a latitude of 22º in winter or a latitude of 68º in summer, which is the difference between Cuba and Iceland. In practice, though, o3 can often figure out the season from how the trees look and how people are dressed.
When we move away from noon, things get a bit more complicated. We have to employ h, the hour angle, which is equal to 0º at local noon (when the sun is directly overhead) and increments by 15º every hour. Here’s the full equation:
sin(θ) = sin(φ)*sin(δ) + cos(φ)*cos(δ)*cos(h)
(o3 says “It’s just the spherical-law-of-cosines applied to the right-angled triangle on the celestial sphere.” Trivial! If you’re curious how we go from this to the simplified noon equation above, see Appendix A.)
This is a bit too involved for a GeoGuessr game—even o3 solves this in a Python environment. Qualitatively, though, this means that as we move away from noon and cos(h) becomes smaller, the solar elevation θ shrinks. Within an hour or two of noon, we’re only off by 1–2º, but after three hours we’re overestimating the latitude by 7–10º.
This seems bad, but with a compass it’s relatively easy to check how far from noon it is. Shadows point exactly north–south at local noon, and point increasingly east or west as the hour angle increases, so looking at the shadow orientation can tell you how much to update the latitude. In practice o3 recommends just ignoring shadow-related math after 3:00 PM or before 9:00 AM, since the error becomes too high.
Here’s another recent GeoGuessr picture. Can we solve this now?
Here’s my attempt to apply what we’ve learned: we’re looking east and we can see that shadows are pointing north, so the sun is south. This means that we’re in the northern hemisphere. The shadow–height ratio is a bit tough to estimate from this picture; based on the Prius, maybe 0.5:1. So that gives us an equinox latitude of 27º N, minus say 5º for time of day (since the shadows seem angled), which leaves us with a latitude of… 22º N ± 23º depending on the season. Not terribly helpful.
I gave o3 the same image, and it told me the latitude was 12–15º N. The correct answer is 11.5 ºN (Phnom Penh).
What did we do wrong? I asked o3 what went wrong with my reasoning, and this is what it told me (lightly edited for clarity):
The last point deserves further discussion. The impact of solar declination on solar elevation is modulated both by the latitude and the hour angle—the 23.4º swing is scaled by cos(h) and by the actual latitude. With some Python code (Appendix B), we can quickly confirm that the effect is smaller at near-equatorial latitudes:
Overall, though, there’s nothing here we haven’t already discussed; o3 just understands this material better than me and can do the math properly.
This is a fun activity for building AI-related intuition. o3 is very good at this and is able to do something that appears superhuman. Upon close inspection, the reasoning is legible, but I’m not really able to follow the same methods myself with any degree of precision; I’m just not quite able to do any step with sufficient accuracy. I’m hoping that I’ll be able to build up my skills over time—this would be a pretty fun party trick.
The full equation is:
sin(θ) = sin(φ)*sin(δ) + cos(φ)*cos(δ)*cos(h)
If we assume that we’re at local noon, cos(h) = 1. This lets us apply the following identity:
cos(α−β) = cos(α)*cos(β) + sin(α)*sin(β)
To get:
sin(θ) = cos(φ - δ)
sin(θ) = sin(90º - φ + δ)
θ = 90º - φ + δ
Which is the simplified equation I presented above.
Here's the Python code to generate the above plot.
import numpy as np import matplotlib.pyplot as plt %matplotlib inline %config InlineBackend.figure_format = 'retina' def solar_elevation_vs_month(hour_local: float, latitude_deg: float) -> np.ndarray: """ Return an array of solar-elevation angles (degrees) for each day at a given true-solar hour and latitude. """ lat = np.deg2rad(latitude_deg) doy = np.arange(365) # Approximate solar-declination model (±23.44° sine fit) delta = np.arcsin(np.sin(np.deg2rad(23.44)) * np.sin(2 * np.pi / 365 * (doy - 80))) # Hour angle: 0° at solar noon; +15° per hour in the afternoon H = np.deg2rad((hour_local - 12.0) * 15.0) # Solar-elevation formula h = np.arcsin(np.sin(lat) * np.sin(delta) + np.cos(lat) * np.cos(delta) * np.cos(H)) return np.rad2deg(h) if __name__ == "__main__": plt.figure(figsize=(6, 4)) months = np.array([15, 46, 75, 105, 135, 162, 198, 228, 258, 288, 318, 344]) for h, l in [(15, 0), (15, 15), (15, 30), (15, 45), (15,60)]: elevations = solar_elevation_vs_month(h, l) plt.plot(np.arange(365), elevations, label=f"{h:.1f} h, {l:.1f}° N") plt.xticks(months, ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']) plt.ylabel(f"Solar elevation (°)") plt.xlim(0,365) plt.ylim(0,60) plt.legend() plt.tight_layout() plt.show()
I did not enjoy John Mark Comer’s book The Ruthless Elimination of Hurry.
Comer’s book is written to people trying to find meaning in a world that feels rushed, distracted, and isolated. At the time of writing, Comer was a Protestant pastor in Portland, Oregon, but he’s since stepped back to focus on creating resources for spiritual formation (like this book).
The book takes its title from a quote by Christian philosopher Dallas Willard:
Hurry is the great enemy of spiritual life in our day. You must ruthlessly eliminate hurry from your life.
In the book, Comer argues that hurry is incompatible with love (p. 23), patience (p. 23), joy (p. 25), peace (p. 25), wisdom (p. 52), and gratitude (p. 52). Hurry is a sign that we aren’t accepting our God-given limitations (p. 65), and hurrying causes irritability, restlessness, distraction, and isolation (pp. 27, 58, 89).
The base state of man before the modern era, Comer argues, was unhurried (pp. 42–45). God rests in Genesis 1, and Jesus himself never hurried. The cure to our modern malaise, thus, is to embrace a life marked by slowness, solitude, simplicity, prayer, and rest. Comer advocates taking a literal 24-hour sabbath, rejecting consumerism, and trying to find a “perpetual Zen-like state of Jesus-derived joy and peace” (p. 251).
Much of what Comer says is good. But the central argument of his piece, that hurrying should be eliminated, doesn’t seem defensible to me. Comer makes very few direct arguments that hurrying itself is bad, instead using hurrying as a metonym for a vaguely defined bucket of negative modern traits: isolation, anxiety, fear, and so on.
This is a classic example of a motte-and-bailey argument, popularized by Scott Alexander on Slate Star Codex. In his post “All In All, Another Brick In The Motte,” he defines a motte-and-bailey argument thusly:
So the motte-and-bailey doctrine is when you make a bold, controversial statement. Then when somebody challenges you, you retreat to an obvious, uncontroversial statement, and say that was what you meant all along, so you’re clearly right and they’re silly for challenging you. Then when the argument is over you go back to making the bold, controversial statement.
Comer does exactly this. “You must ruthlessly eliminate hurrying” is a bold, controversial statement—but the statement he actually defends is more like “anxiety and isolation are bad,” which doesn’t have quite the same transformative implications for modern Christian living.
I’ll go a step further and try to defend the assertion that hurrying can be good, actually. Here’s Genesis 18:1–8 (ESV), when Abraham receives God at Mamre (emphasis added):
And the Lord appeared to Abraham by the oaks of Mamre, as he sat at the door of his tent in the heat of the day. He lifted up his eyes and looked, and behold, three men were standing in front of him. When he saw them, he ran from the tent door to meet them and bowed himself to the earth and said, “O Lord, if I have found favor in your sight, do not pass by your servant. Let a little water be brought, and wash your feet, and rest yourselves under the tree, while I bring a morsel of bread, that you may refresh yourselves, and after that you may pass on—since you have come to your servant.” So they said, “Do as you have said.” And Abraham went quickly into the tent to Sarah and said, “Quick! Three seahs of fine flour! Knead it, and make cakes.” And Abraham ran to the herd and took a calf, tender and good, and gave it to a young man, who prepared it quickly. Then he took curds and milk and the calf that he had prepared, and set it before them. And he stood by them under the tree while they ate.
In the above passage, Abraham hurries and tells others to hurry. I think it’s pretty clear from context that Abraham’s behavior here is correct hospitality, not sinful, since God immediately blesses Abraham (v. 10) and says he’s been chosen ”to keep the way of the Lord by doing righteousness and justice” (v. 19).
Here’s a few other passages defending the practice of hurrying, which I will summarize for brevity’s sake:
These points may seem obvious—clearly, if Amalekite raiders carried off your family, you would hurry after them! But even a trivial example like this demonstrates that hurrying is not intrinsically opposed to the will of God.
Comer also argues that Jesus himself never hurried (p. 92). This is debatable—Mark uses the word “immediately” to describe many of Jesus’s actions, but that may reflect a Markean narrative style more than the actual pace of actions. Jesus himself commands Zaccheus to hurry (Luke 19:5), and his anger cleansing the temple and agony at Gethsemane should be sufficient to dispel the idea that Jesus perpetually existed in a “Zen-like” (p. 251) stoic state. Stress is not incompatible with sanctification.
Comer further argues that we’re called to walk with God, not run with him (p. 23). This is quite literally false! In 1 Corinthians 9:25–27, Paul uses the metaphor of running to convey the discipline, self-control, and perseverance required for mature Christian living:
Do you not know that in a race all the runners run, but only one receives the prize? So run that you may obtain it. Every athlete exercises self-control in all things. They do it to receive a perishable wreath, but we an imperishable. So I do not run aimlessly; I do not box as one beating the air. But I discipline my body and keep it under control, lest after preaching to others I myself should be disqualified.
Equating maturity with a restful, non-stressed life sets Christians up for disappointment. Hebrews 11 specifically highlights the heroes “of whom the world was not worthy” who “went about in skins of sheep and goats, destitute, afflicted, mistreated” (vv. 37–38). A stressful life doesn’t mean, as Comer argues, that “something is out of whack” (p. 85). God’s rest will come, but it might not come today.
The conclusion here is not that we should hurry more. Most people hurry for bad reasons, stuck chasing selfish desires or climbing social ladders in pursuit of an elusive fulfillment. But the call of Christianity is not to abnegate these bad desires but to align them with God’s will. As Rene Girard says in I Saw Satan Fall Like Lightning (p. 13, emphasis original):
What Jesus invites us to imitate is his own desire, the spirit that directs towards the goal on which his intention is fixed: to resemble God the Father as much as possible.
What we are willing to hurry for reflects what we care about. We see this reflected in the passages above: Abraham hurries to show hospitality and welcome God into his life, David hurries to save the captives, and Mary & Philip hurry to share the good news of the gospel. We should care enough about these things that we’re willing to hurry for them.
Comer’s call to a life of virtue, peace, and rest is excellent—and at the margin, he’s probably right that most people should hurry less in their lives. But the central claim of the book is just not correct. The vision of Christian maturity contained in The Ruthless Elimination of Hurry seems a little too close to California-style Zen Buddhism and other modern mystical practices to fully align with Scripture, and I think this is bad.
Thanks to Taylor Wagen, Tony Robinson, Elias Mann, Jonathon Vandezande, and Chloe Wagen for helpful discussions, and for Ari Wagen for originally pointing me to read Girard.