<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://nadiah.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://nadiah.org/" rel="alternate" type="text/html" /><updated>2026-06-03T21:43:12+00:00</updated><id>https://nadiah.org/feed.xml</id><title type="html">Nadiah Pardede Kristensen</title><subtitle></subtitle><entry><title type="html">Choose your own maths adventure - interactive online worksheets</title><link href="https://nadiah.org/2025/05/14/nudge-system" rel="alternate" type="text/html" title="Choose your own maths adventure - interactive online worksheets" /><published>2026-05-14T01:44:54+00:00</published><updated>2026-05-14T01:44:54+00:00</updated><id>https://nadiah.org/2025/05/14/nudge-system</id><content type="html" xml:base="https://nadiah.org/2025/05/14/nudge-system"><![CDATA[<p>Students miss out on in-person workshops for all kinds of reasons, including work and caring responsibilities. 
It’s not enough to supply worked solutions because that doesn’t facilitate the <em>productive struggle</em> that deepens understanding and builds self-efficacy.</p>

<p>Thinking about this problem,
I started thinking about point-and-click adventure games– they are good at facilitating productive struggle.
They let you try and fail, and they nudge you forward without handing the answer to you. 
They make you want to persist.
I wondered if we could do something like that.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2026/05/the_room.jpg">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2026/05/the_room.jpg" alt="The Room - Fireproof Games" />
    </a>
    
    <figcaption><span><p>The Room - Fireproof Games</p>
</span></figcaption>
</figure>

<p>This semester,
I built two interactive linear algebra worksheets for MXB106: two weeks, two multipart questions.
I embedded them into Canvas, the learning management system used at QUT.
You can visit them here: <a href="https://nadiah.org/nudge/mxb106/nonhomogeneous_systems.html">Week 8</a>, 
<a href="https://nadiah.org/nudge/mxb106/orthogonality.html">Week 9</a>.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2026/05/embedded_in_canvas.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2026/05/embedded_in_canvas.png" alt="Interactive linear algebra worksheets embedded into Canvas." />
    </a>
    
    <figcaption><span><p>Interactive linear algebra worksheets embedded into Canvas.</p>
</span></figcaption>
</figure>

<p>The key design feature was branching paths: 
the student chooses their level of scaffolding and decides where to go next.</p>

<p>Hints are nested, to reveal gradually, so students retain the satisfaction of solving the problem.</p>

<p>And when the student gets the answer wrong,
the response isn’t “Wrong, try again”, 
it’s “We’re going to learn this together”, e.g., with a step-by-step guide.</p>

<div style="display: flex; flex-wrap: wrap; gap: 16px; width: 100%; margin: 20px 0;">
  <iframe src="https://nadiah.org/nudge/mxb106/nonhomogeneous_systems.html" style="flex: 1; min-width: 300px; height: 500px; border: 3px solid #ccc;"></iframe>
  <iframe src="https://nadiah.org/nudge/mxb106/orthogonality.html" style="flex: 1; min-width: 300px; height: 500px; border: 3px solid #ccc;"></iframe>
</div>

<p>I also collected detailed anonymised tracking data — I know exactly which path each student took,
where they clicked, and when — so I could watch exactly how it worked.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2026/05/one_student_journey.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2026/05/one_student_journey.png" alt="Detailed tracking data for one student. Red triangles are clicks on hints and within-page questions and answers. This student was progressing rapidly until part 4, where they got stuck. They took two quick peeks at the step-by-step guide, but decided to answer the question alone and got it wrong. They were then sent back and spent around 5 minutes progressing through the step-by-step guide until they got the answer right. They then rewarded themselves with a 13 minute break before rapidly finishing the rest of the worksheet." />
    </a>
    
    <figcaption><span><p>Detailed tracking data for one student. Red triangles are clicks on hints and within-page questions and answers. This student was progressing rapidly until part 4, where they got stuck. They took two quick peeks at the step-by-step guide, but decided to answer the question alone and got it wrong. They were then sent back and spent around 5 minutes progressing through the step-by-step guide until they got the answer right. They then rewarded themselves with a 13 minute break before rapidly finishing the rest of the worksheet.</p>
</span></figcaption>
</figure>

<p>Remember my main goal was to reach students who are unable to attend in-person workshops.
As luck would have it, in the second week of the trial, the Monday workshop was cancelled due to ANZAC Day– 
and that’s where he had our second-largest spike of activity.
When students couldn’t get to their workshop, they used the interactive online version instead.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2026/05/timeline.svg">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2026/05/timeline.svg" alt="The timeline of events leading up to the assignment showing interactions with the Week 8 and Week 9 worksheets. In-person workshop hours are shaded in red, and the first spike of activity occurs during the first workshop. In the second week, the Monday workshop was cancelled due to ANZAC Day (in yellow), and that was where the second spike of activity occurred. When students were unable to attend their in-person workshop, they used the online worksheet instead." />
    </a>
    
    <figcaption><span><p>The timeline of events leading up to the assignment showing interactions with the Week 8 and Week 9 worksheets. In-person workshop hours are shaded in red, and the first spike of activity occurs during the first workshop. In the second week, the Monday workshop was cancelled due to ANZAC Day (in yellow), and that was where the second spike of activity occurred. When students were unable to attend their in-person workshop, they used the online worksheet instead.</p>
</span></figcaption>
</figure>

<p>With the detailed tracking data, I was able to focus in on students who fully engaged with the worksheets and worked outside workshop hours.
In the first week, 40% worked outside workshop hours; in the second week, 85% did.</p>

<p>I could also see the hours when they were working. We had some students doing linear algebra at 1 am in the morning.</p>

<p>When I tightened the data again to look at those working <em>exclusively</em> outside workshop hours and <em>deeply</em> engaged, 
I estimate we picked up about 45 students, which is equivalent to about 2 or 3 in-person workshops.</p>

<p>But did it work?</p>

<p>For this pilot trial, we had selected questions from the existing worksheets that addressed the upcoming assignment in some way.
Compared to previous years,
marks improved on every question (all statistically significant, BH p &lt; 0.03) with an overall improvement of 12 percentage points.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2026/05/assignment_results.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2026/05/assignment_results.png" alt="Marks improved on every question, all statistically significant, with an overall improvement of 12 percentage points." />
    </a>
    
    <figcaption><span><p>Marks improved on every question, all statistically significant, with an overall improvement of 12 percentage points.</p>
</span></figcaption>
</figure>

<p>We also had some positive qualitative feedback.
One student emailed to request more worksheets,
and the in-person feedback was positive as well.</p>

<figure style="max-width: 700px; margin: auto;">
    
    <a href="/wp-content/uploads/2026/05/feedback.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2026/05/feedback.png" alt="" />
    </a>
    
</figure>

<p>So it seems the online worksheets not only improved marks, students also found them valuable.</p>

<h3 id="further-reading">Further reading</h3>

<p>I presented talk today at the Faculty Learning &amp; Teaching Colloquium about the worksheets.
A link to the presentation slides is <a href="https://nadiah.org/nudge/presentation/">here</a>.</p>]]></content><author><name>nadiah_kristensen</name></author><category term="teaching" /><summary type="html"><![CDATA[Students miss out on in-person workshops for all kinds of reasons, including work and caring responsibilities. It’s not enough to supply worked solutions because that doesn’t facilitate the productive struggle that deepens understanding and builds self-efficacy.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://nadiah.org/wp-content/uploads/2026/05/the_room.jpg" /><media:content medium="image" url="https://nadiah.org/wp-content/uploads/2026/05/the_room.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Lecture notes on stability in the dynamics and Euler’s method</title><link href="https://nadiah.org/2025/12/04/mxb261" rel="alternate" type="text/html" title="Lecture notes on stability in the dynamics and Euler’s method" /><published>2025-12-04T01:44:54+00:00</published><updated>2025-12-04T01:44:54+00:00</updated><id>https://nadiah.org/2025/12/04/mxb261</id><content type="html" xml:base="https://nadiah.org/2025/12/04/mxb261"><![CDATA[<p>This semester, 
I had the opportunity to do 7 weeks of “interactive lectures” at QUT for 
<a href="https://www.qut.edu.au/study/unit?unitCode=MXB261">MXB261 Modelling and Simulation Science</a>,
as well as one 
<a href="https://nadiah.org/wp-content/uploads/2025/11/game_theory_ad4_shorter.pdf">guest lecture on evolutionary game theory</a>.
In this blog post,
I’ll talk about my experiences, share the first half of one of the lectures I gave,
and provide the <a href="https://github.com/nadiahpk/nadiahpk.github.io/tree/main/wp-content/uploads/2025/12">code</a>,
to generate the figures, in case any of that is useful to anyone.</p>

<p>The Unit Coordinator is transforming the MXB261 into a game theory unit,
and the first 7 weeks were “old” material she will replace in the coming years.
Students responded really well to the new game theory content,
describing it in the survey as “fascinating” and “mind blowing”.</p>

<p>Nonetheless, the old material was also quite interesting;
one student still wanted to “go down the ODE rabbit hole” after the exam.
The unit had no maths prerequisites, 
so any second- or third-year student curious about modelling could join,
and it offered a “taster” of different modelling techniques, 
including Monte Carlo simulation, Markov processes, and ODE models.
The case studies were focused on ecological applications,
and it attracted students from maths, data science, and environmental science.
I think it was quite unique with its goal of broadening students’ 
knowledge rather than going deep on a particular topic,
and I would have really enjoyed it as a second-year undergrad (I did environmental engineering).
I hope that the students will draw on it later in their professional career;
if they have a particular problem to solve, 
I hope they will remember which types of techniques can be used to solve it,
and build from the basic principles they learned here 
to teach themselves the rest of what they need to know.</p>

<p>The interactive lectures were “hybrid” lectures with students attending both in-person and online.
The first half was typically structured more like a traditional lecture but with 
active-learning opportunities.
I would work through additional material and the self-study activity,
but pepper it with Q&amp;A and pair-and-share.
The second half was space for students to ask questions and get help with their assignments.</p>

<p>One challenge was teaching to students from a range of backgrounds.
Some students found the MATLAB coding trivial but were thrown by mathematical notation,
while others felt at ease with the mathematics but struggled with coding.
So before each interactive lecture,
I would review the activity and workshops,
watch the online recorded lecture,
and think about my 2 imaginary students:
one who is good at maths but struggles with coding, 
and another who is comfortable with coding but not with maths.
What is the implicitly assumed knowledge they might not have?
What would they find unfamiliar and difficult?
That would guide my content for the interactive lecture.</p>

<p>Below, 
I will share the first half of one lecture I gave.
For this lecture,
I noticed that the online recordings assumed that students would be familiar 
with initial-value problems and stability.
For example,
the stability conditions were given without an explanation for why they were so.
So my lecture introduces quiver plots as a visual tool for understanding ODEs, 
then uses them to build intuition for steady states and stability, 
before moving to cobweb plots to explain numerical stability in Euler’s method.</p>

<p>It took quite a while to prepare this lecture,
so perhaps this will save someone some time.
The full list of MATLAB scripts used to create the figures is available from 
<a href="https://github.com/nadiahpk/nadiahpk.github.io/tree/main/wp-content/uploads/2025/12">this blog’s repository</a>,
and I’ll link them in context below.
One thing I found really helpful was to plot a thin grey line where I intended to draw during the lecture.
The lines aren’t visible on the slides below, but were visible on the tablet I used, 
and that’s much safer than trying to free-hand it.</p>

<p>Although it might embarrass me a little,
I’ve decided not to edit out my verbal hiccups.
The transcript is pretty much how Zoom transcribed it 
except I removed some exchanges I had with students,
added ellipses and removed muttering when I pause to draw, 
and corrected some Zoom-transcription errors.
I’ve benefitted from looking at the transcripts of others’ lectures 
and seeing those verbal imperfections.
The transcript also helped me notice a few verbal tics I would like to correct.
I have almost succeeded in removing “um” and “like” from my vocabulary,
but it’s a journey.
I think I could reduce the number of sentences I start with “So…”.
The main thing I would like to correct is my use of gendered language (“you guys”).
This is not a good habit, particularly when I’m teaching maths.</p>

<h3 id="lecture-transcript">Lecture transcript</h3>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-001.png" alt="" /></p>

<p>In your online lectures, you looked at deterministic population growth models,
unconstrained growth, constrained growth. Then you looked at Euler’s method, 
and then a little bit of cool stuff with period doubling and chaos.</p>

<p>So, there’s no prerequisites for this course, and I know that,
if that was the first time I’d seen this kind of thing, I would have been pretty bewildered. 
There’s certainly a lot of guidance on how to use Euler’s method and stuff like that, 
but learning is more than doing the activity, it’s understanding the meaning behind it.
And so what I thought I’d do for this interactive lecture is introduce you to some visual tools 
to help you understand what’s really going on behind this maths.</p>

<p>So for those of you whose expertise is in coding and not so much in maths, 
I hope that this will be useful for you.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-002.png" alt="" /></p>

<p>So it’s actually a legal requirement that any lecturer who talks about stability 
has to use the balls on a hill analogy. 
So, I’m going to do that. I’m going to consider two balls on a landscape.
I’ll give this one an open circle, and this one a closed circle,
and this is an analogy for the concept of steady state and stability.</p>

<p>In this, analogy, the ball position is like a system state. 
So we could imagine that the position here is Y, for instance, and gravity provides us our system dynamics. 
Now, these two balls, one perched on the hill and one in the valley, are both at steady state.
So the meaning of steady state is that something doesn’t change with time. 
And both of these balls, as we watch them … they’re not moving.</p>

<p>But there is a sense that one of these balls is more stable than the other. 
Intuitively, a stable steady state would be the example of the ball at the bottom of the hill, 
that if we give it a small perturbation, if I pushed to the left, it would roll back to where it was.
Whereas an unstable steady state is the ball at the top of the hill, 
where if I give it a bit of a push, 
then it’ll roll off down the hill and go away from that steady state that it started at.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-003.png" alt="" /></p>

<p>So in your online lectures, 
this question of stability was discussed in the context of two distinct aspects of the 
population growth modelling that you were looking at. What were those aspects? 
I’m going to get you to pair-and-share. 
And I am going to try something exciting and create a breakout room for the people online
so that you can discuss as well, so hold on to your socks.</p>

<p>(Pair-and-share. No one was able to answer)</p>

<p>Fair enough. Okay, well, this is a great lecture, 
because I’m going to show you exactly what those two aspects of stability were. 
So I’ll just write them here…
The two aspects of stability were
the dynamics of the model itself. So the stability of the population.
And the other aspect of stability was the stability of the numerical solution to the dynamics.
So, in this lecture, 
I want to introduce some kind of visual tools to help us get a handle on these two aspects of stability.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-004.png" alt="" /></p>

<p>We’re given a class of problems called the initial value problem. 
And so the idea is that you’re given some kind of differential equation. 
These equations describe how the state variables change over time. 
So, in this case, the population size changes over time.
And we’re interested in finding the trajectory of the population given the initial population size, 
given the initial values. 
And so, in this case, we start with Y at T equals 0 equals YO, 
and in the online slides, you are given these solutions to YT.</p>

<p>But I think maybe it might not have been so clear where these solutions came from. 
Certainly these examples have analytic solutions, but when we’re modelling– 
I mean, if we’ve got an analytic solution, we’re not going to break out the MATLAB, right? 
We just write it on a piece of paper. 
And so usually, when you’re using these sorts of techniques, you don’t have an analytic solution.
And it’s not super obvious where these solutions came from.</p>

<p>So, what I want to do is first introduce you a tool called the quiver plot. 
And this is a visual tool for understanding how the system dynamics works.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-005.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_one_quiver.m">plot_one_quiver.m</a></p>

<p>So, in this case, I’m going to look at the unconstrained growth, model. 
So dY dT equals lambda y, and I’ve just set lambda equal to 0.3.
So the idea of the quiver plot is that we, say, start at a point, say y equals 3, T equals 2,
What we’re going to do is we’re going to take a delta T.
In this case, I’ll set delta T to 1. 
And then we calculate, with that delta T, what will be our delta Y?
So, we calculate our delta Y, is equal to 0.3 Y delta T.
And so that would be, in this case, 0.3 times 3 times 1, because I set this one to 1.
And so that’s how we get the length of this arrow here.</p>

<p>And then we basically draw an arrow in space, like that, on our graph. 
So here was Y equals 3, T equals 2, so that’s that point.
And so, if I move along in time by one step, then I go up by 
… sorry, I forgot to do the final calculation … 0.9.
Then we move up by 0.9, so up to there, so we draw a little arrow like this.</p>

<p>And so we can do that at various points in the grid. For example, let’s start here with
Y equals 6, T equals 4. So we’re gonna take a step of T equals 1, 
and so the height of Y will be, let’s see, 6 times 0.3 is 1.8, so we’ve got to go to there.
So we draw an arrow like that.</p>

<p>And we can do this in every spot on our grid to get a kind of sense of the dynamics…
This is an important one. When Y equals zero, always the change in Y will be equal to 0.</p>

<p>And so imagine that I did this for the entire space of Y versus T. 
And I end up with this plot, which is called the quiver plot.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-006.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_expon_growth.m">plot_expon_growth.m</a></p>

<p>And this is fantastic, because this <em>is</em> DYDT equals lambda y. 
These little arrows are like a visual representation of the differential equation 
that you can see with your human eyeballs.</p>

<p>In addition, it has this very intuitive feeling about it, like there’s a flow of Y in time. 
And that intuition is correct. 
We can basically use that flow to understand where our solutions to DYDT equals lambda y 
goes from various initial values.</p>

<p>So let me be concrete about that. 
Let’s say I want to start at Y0 equals 1.
So I put a little dot here.
Now, you can see that there’s an arrow in that direction, 
and so I’m going to draw a line in that direction. 
This is Y changing with time.
As Y gets larger, it gets entrained into these arrows that are headed more upwards.
And so it increases … at an increasing rate … like this. 
And so that’s it, that’s one solution to the initial value problem, YO equals 1.</p>

<p>We can do another one.
Let’s say we want to start at YO equals 2 … we catch our arrow, and we follow those arrows through time.</p>

<p>Here’s YO equals 4.</p>

<p>And of course, a pretty important one, Y equals zero … 
Our arrows are just going along like this, through time.</p>

<p>And so that’s how the initial value problem works, that dy dt gives us this … 
well, there’s an infinite number of arrows, but I can only draw so many. 
The initial value gives us where our solution starts, and the differential equation 
how we’re going to follow that flow through time.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-007.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_expon_decline.m">plot_expon_decline.m</a></p>

<p>So that was the unconstrained growth model. 
Here’s another example with population decay. 
So this is the quiver plot, but it’s pointing in the other direction now, 
because our lambda is a negative number here. 
And we can do the same thing again.</p>

<p>So let’s start at YO equals 10, and we’re following our arrows down… 
And that’s one of our solutions to the problem.</p>

<p>If Y equals 4, start at 4, Following it down … 
And that’s another way that the population might change over time.
Here’s another one.
And if we have zero … then they will stay zero forever.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-008.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_logistic.m">plot_logistic.m</a></p>

<p>This one is the dynamics of the constrained growth, and it’s a bit more exciting 
because we’ve got arrows going in different directions, but the same principle applies.
So we start at YO equals 0 … If we have no animals, there’ll be none forever.</p>

<p>Y equals 2 …</p>

<p>This is a special value: this is 8, the carrying capacity. 
And so if we’re at carrying capacity, we stay at carrying capacity forever.</p>

<p>And if we’re above it, well, that’s too many, so some … some animals are going to die…
it’s the generations turn over, and we’ll go back down to carrying capacity.</p>

<p>So that’s where those lines came from in the online lectures. 
You can solve them analytically, but in the cases where you cannot, 
this is a kind of visual understanding of where the solution comes from.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-009.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_expon_growth_compact.m">plot_expon_growth_compact.m</a></p>

<p>So, remember, our analogy of the balls?
We’ve got the unstable ball here … The stable ball here.</p>

<p>The first question we ask ourselves is, well, 
in the analogy, both of the balls were at steady state, 
because if we watch them through time, they don’t change.</p>

<p>And so, is there some point in this, in this flow, 
where if Y is at that value, it doesn’t change through time?
And so, we’re looking at this, and … evidently,
if we start at zero … we stay at zero forever. 
And so we will call this our steady state.
Indicate it with a star … equals zero.</p>

<p>Now, the next question, is it stable or unstable?</p>

<p>So, when we’re thinking about our balls, if we give them a bit of a push, 
do they come back to where they were, or do they roll off to somewhere else?</p>

<p>So let’s give our steady state a bit of a push… 
And it gets entrained by an arrow that’s ever so slightly going upwards.
And then it rolls away from y equals 0.
So that means that Y star equals 0 is unstable.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-010.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_expon_growth_compact.m">plot_expon_growth_compact.m</a>,
<a href="/wp-content/uploads/2025/12/plot_expon_decline_compact.m">plot_expon_decline_compact.m</a>,
<a href="/wp-content/uploads/2025/12/plot_logistic_compact.m">plot_logistic_compact.m</a></p>

<p>So I just did the unconstrained growth example, and I think that, 
as a pair-and-share, you guys could do the other three cases.</p>

<p>So do the population decay and do the constrained growth, 
identify the steady states first, and then mark them unstable or stable.
Alright, I’m gonna try this breakout rooms thing again.</p>

<p>(Pair-and-share)</p>

<p><em>Okay, yeah, so, so what did we conclude for the population decay one? What’s the steady state for that one? Plug it into the chat or shout out. Y is 0. Yeah, awesome, yes, exactly!</em></p>

<p><em>So in Y-Star, Equals zero, that’s our steady state. And is this one stable or unstable? Will be stable? Chat? You didn’t hear that answer, so have you guys got an answer to whether it’s stable or unstable? Stable, awesome, yeah! So you’ve got the idea that if you give this a bit of a push. Then it’s going to come back to where it was before.</em></p>

<p><em>Okay, now this one’s a bit more complicated. It’s got two steady states. Let’s just pick one. What’s one of the steady states? That’s the full answer from the chat! Real fast. Yes, excellent. So here’s a … steady state, Y star equals 0. And this one is unstable, because if we give it a bit of a push, it’ll get entrained into those, arrows and move away. And then there’s Y star equals H, which was our carrying capacity, and this one’s stable. And so you can see that if we push it up or down, it’s going to come back to where it was before.</em></p>

<p>Alright, so we’ve got our intuition nice and primed!</p>

<p>But we’re probably not going to be plotting quiver plots to understand the stability of our system. 
So, is there some kind of mathematical way that we can capture this intuition and do it with maths?</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-011.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_logistic_compact.m">plot_logistic_compact.m</a></p>

<p>So, steady state means that there’s no change in time.
And so that means that we’re looking for a DYDT that stays zero for the, entire time.</p>

<p>So I’ve got a bit of new notation here that might be new to you. 
What this means is it’s “evaluated at”. 
So I’m going to calculate DYDT, 
but I’m going to evaluate it at the particular Y star that it will be equal to 0 at.</p>

<p>That was a bit of a clunky explanation. 
Your online lecturer uses FY star equals 0 instead…
I find F not very descriptive, so I’m going to keep using this notation here.</p>

<p>So let me give you a concrete example. 
Here’s the constrained growth. There’s my DYDT. 
And so what I do is I substitute in everywhere where there’s a Y, I’m going to put in a Y star.
And I’m going to make that equal to zero, and then I’m going to try to solve it.</p>

<p>What are the Y stars that make this equal to zero?
So, we’ve got a product here, so that means that it can be zero if either this part is equal to zero, 
or that part is equal to zero.
So this left-hand side, that’s equal to zero when y star itself is equal to zero, 
so that’s that solution there.
And this right-hand side, well, that would be equal to 0 if Y star on 8 is equal to 1, 
which means that Y star is equal to 8, and so that’s… So that’s how we get our steady states
mathematically.</p>

<p>Okay, so we’ve got our steady states. 
But how can we tell if they’re stable or unstable? 
We’ve been looking at the quiver plot so far, 
but is there some mathematical way to check if it’s stable or unstable as well?</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-012.png" alt="" /></p>

<p>And so, in your online lectures, you’re given, in particular, this part here is the important bit.
So apparently, if this derivative is less than zero, then it’s stable.</p>

<p>Okay, cool. So we could memorize that, but why is that the case? 
Like, can we get some intuition going for this?</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-013.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_expon_growth_compact.m">plot_expon_growth_compact.m</a>,
<a href="/wp-content/uploads/2025/12/plot_expon_growth_dydt.m">plot_expon_growth_dydt.m</a></p>

<p>So, to get the intuition for this, I’m going to do something, a little bit new. 
This is the plot that I had before. Here, DYDT versus Y.
And so I’m looking at my unconstrained growth again.</p>

<p>And this is the line that I’m interested in plotting… DYDT versus Y.</p>

<p>When y equals 0, DYDT equals 0, so we’ll start here.
And what’s an easy second point? 
When y equals 10, DYDT equals 3, so we’re going to that point there.
So this is a straight line.
So that’s our DYDT versus Y.</p>

<p>So now, what I’m going to do is I’m going to draw a cartoon of the dynamics on this axis here.
And, this is something that’s commonly done in the literature,
I’m going to use the same conventions as them.</p>

<p>So, what I’m going to do is, on this axis, on this line, 
I’m going to draw an arrow that goes in the positive direction, if there’s a positive change.
If DYDT has, if Y has a positive change, or DYDT is positive.
And I’m going to draw an arrow going in the negative direction, if it’s negative.</p>

<p>And I’m also going to identify the steady states by drawing an open circle … for our unstable states, 
and a closed circle … for our stable states. So it’s the same as the balls before.
I planned ahead.</p>

<p>Alright, so how do we do this? 
So, I’m looking at DYDT, and I see that it’s positive, so I’m going to draw an arrow like this.</p>

<p>I could go a little bit further. I know that then when Y is quite large, the arrow is quite large.
But then Y is small, the arrow is small. 
And indeed, DYDT here is high when it’s high and low when it’s low. 
So I could draw a … big arrow here, and a small arrow here, 
to kind of indicate that.</p>

<p>And so I know that the steady state is where Y equals 0, and at this point, 
I can actually just stop thinking and look at where the arrows are pointing. 
The arrows are pointing away from the steady state. So that means there’s an unstable steady state. 
So I’m going to put an open circle right here.</p>

<p>So this is my cartoon for the dynamics.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-014.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_expon_decline_compact.m">plot_expon_decline_compact.m</a>,
<a href="/wp-content/uploads/2025/12/plot_expon_decline_dydt.m">plot_expon_decline_dydt.m</a></p>

<p>Do the same thing for the population decay.
So, we’re going to draw a… DYDT on this axis, and Y on that axis.
So when Y is 0, that equals 0.
This time it’s a negative 3 with y equals 10.
And I see that everywhere … this is negative, so I’m going to draw the negative arrow.
And so the arrow is pointing towards my steady state, and so I’m going to put a stable steady state there.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-015.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_logistic_compact.m">plot_logistic_compact.m</a>,
<a href="/wp-content/uploads/2025/12/plot_logistic_dydt.m">plot_logistic_dydt.m</a></p>

<p>This is the fun one.
So this, DYDT, it ends up being a bit of a curve …
that starts here … and then goes down like this.</p>

<p>I think the purpose of all of these sorts of things is to not have to think too hard. 
So what have we got? We’ve got positive here … in here, it’s negative.
And so immediately, I’ve got it: unstable steady-state, stable steady-state.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-016.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_expon_growth_compact.m">plot_expon_growth_compact.m</a>,
<a href="/wp-content/uploads/2025/12/plot_expon_decline_compact.m">plot_expon_decline_compact.m</a>,
<a href="/wp-content/uploads/2025/12/plot_logistic_compact.m">plot_logistic_compact.m</a>,
<a href="/wp-content/uploads/2025/12/plot_expon_growth_stability.m">plot_expon_growth_stability.m</a>,
<a href="/wp-content/uploads/2025/12/plot_expon_decline_stability.m">plot_expon_decline_stability.m</a>,
<a href="/wp-content/uploads/2025/12/plot_logistic_stability.m">plot_logistic_stability.m</a></p>

<p>Alright, so these are the examples together.
This one had an arrow going that way, that one had an arrow going that way …
this one has arrow going like this.
And in fact, I could have drawn the arrows first, and then decided on the stability.</p>

<p>Are there any questions about that so far?</p>

<p>At this point, we’re just kind of creating a visual tool, 
and it’s going to hopefully get us towards the maths in a moment.</p>

<p>No questions from chat? Okay, we’ll keep going.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-017.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_expon_growth_stability.m">plot_expon_growth_stability.m</a>,
<a href="/wp-content/uploads/2025/12/plot_expon_decline_stability.m">plot_expon_decline_stability.m</a>,
<a href="/wp-content/uploads/2025/12/plot_logistic_stability.m">plot_logistic_stability.m</a></p>

<p>Alright, so those were my examples that I just went through before.
And so what I’m going to do is I’m going to get you to all pair and share. 
I’ve given you an arbitrary DYDT versus Y curve here, and I want you to do the same thing that I did before. 
Find out where the arrows point, and then find out where the steady and the stable 
and unstable steady states are.
We’re gonna do the breakout rooms again. Getting good at this.</p>

<p>(Pair and share)</p>

<p>Alright, so .. help me out here while I do it. 
So, okay, let’s start at this point.
Am I drawing a positive or negative arrow at that point?
I hear a whisper of positive. Heading right, yes. Okay, and how about this one here, in between?
Left! Alright, I think you guys get it.
And so, this one’s positive because the curve is above, and it’s positive, 
and this one’s negative because it’s above it.</p>

<p>And so at this point, we don’t have to think anymore. 
This one’s stable, this one’s unstable, this one’s stable,
this one’s unstable, this one’s stable.</p>

<p>Alright, now there was a second part: 
what is it that distinguishes the stable from the unstable steady states?
With respect to the slope of the blue curve at those steady states? Did anyone notice a pattern?</p>

<p>Hmm. Over to under means stable, and under to over means unstable. Yep, definitely.
So, yes. Over to under means stable. So, over to under.</p>

<p>What would the slope of that be? Would that be positive or negative?
Yeah, you got it. It is, it’s negative. So this one’s got a negative slope.
And this one’s got a positive slope.</p>

<p>I’m talking about the slope of the blue line, right? 
At this point, the blue line has a negative slope. 
At this point, it has a positive slope. At this point, it has a negative slope.</p>

<p>So it looks to us that the thing that distinguishes our stable from our unstable steady states 
is the slope of dydt with respect to Y.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-018.png" alt="" /></p>

<p>Okay, so let’s consolidate all that. When we’ve got a positive slope, it’s unstable. 
When we’ve got a negative slope, it’s stable. 
So how could we write this mathematically?</p>

<p>So, we’ve got DYDT on this axis, and Y on that axis. 
So, slope is d-something d-something, 
and it’s DY on the bottom because that’s the one that’s on our x-axis, 
as confusing as that is.</p>

<p>And what is it the slope of? Well, it’s the slope of DYDT. So this is the … 
this is the quantity that we’re using to determine whether our steady state is stable or unstable.</p>

<p>Now, this function will give us the value of that slope at every point. 
But we’re not interested in, like, the slope here, or here. 
We want it specifically at the steady states, right? 
So then we’re going to use our line notation to say we’re going to evaluate that … 
at Y equals Y star.
So this is one of our Y stars, for instance.
So this here … means evaluated here.</p>

<p>So I’ve written that out here, each of the cases. 
We’ve got a stable steady state when the slope of DYDT with respect to Y evaluated at Y star is negative.
In your online lecture notes, they used a more abbreviated notation … like this … 
where it’s understood that F is DYDT, but I find that kind of counterintuitive. 
I like it to be more verbose, so I know what’s going on.</p>

<p>And likewise, if it’s an unstable steady state, well, we’re looking at that same quantity,
but it has to be positive.
And so, in your lecture notes, it looks like that. 
And so, you know what? Now you understand what that online lecture was saying.</p>

<p>(Student asks: What if the derivative is zero?)</p>

<p>You want to know about the equals zero case, don’t you? Okay, let’s do it.
Alright, so let’s imagine… well, let’s look at an example.
So, if we had … DYDT, and we had Y, 
in order to have it exactly zero, we’re going to need something like this …
at our Y star, it has to be flat. 
And so one way we could do that is we could have it … looking like this.
So what would that look like in our quiver plot?
Let’s say that, I put my Y star here, and this is time.
So now I’m doing Y versus T.
So it means that as we’re approaching Y-STAR …
our, our, … well, let’s see, they were, 
they were quite … and then they kind of went like this … and then they went like that, 
and then they went like that.
But then they go positive again instead of going negative, and so they go up like that. 
So, if you imagine that one trajectory as a huge quiver plot — 
I’ll draw a better picture of this for the annotated notes — 
then that would be what you’re looking at, so it’s a point of inflection.
… All of the examples you’ll be working through, it’s either positive or negative,
so you don’t look at this kind of this in-between-y situation.</p>

<p>Are there any more questions about that? How about you, chat? I’m looking at the window. 
It’s all good. Alright.</p>

<p>Okay, so now we’ve got a mathematical way to do this, 
let’s do an example, a concrete example, so we can really solidify this.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-019.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_logistic_stability.m">plot_logistic_stability.m</a></p>

<p>So, this is our constrained growth again, because it’s my favourite.
And we’ve got the dynamics here, and these steady states we found before. 
They were Y equals 0 and Y equals… Y star equals 8.
And we knew from drawing our picture when it was unstable or stable, 
but let’s do it with maths to verify.</p>

<p>So, we want to look for, it’s stable if DDY of DYDT, evaluated at Y star is less than 0.
So the first thing I do is, I just rearranged it,
I expanded it out, because it’s easier to take the derivative then.
So, I want to take the derivative of Y on 2, so that’s 1 on 2, so that’s that first term there.
And this turns into 2Y on 16, which is Y on 8,
so that’s there, and there’s your negative coming over.
And so, at this step, now we need to — because we don’t want to know about the slope there, 
we want to know about it here and here, at each of the steady states — 
so we’re going to look at each of the steady states.</p>

<p>When Y star equals zero, what we do is we substitute Y equals 0 into this, 
so that would make this part 0, and that would leave a half, so that’s our half there. 
Half is greater than zero, so it’s unstable, which is something we verified before.</p>

<p>Now let’s think about when Y star equals 8. 
So if we put an 8 in here, that’s a 1, and a half minus 1 is a negative half, 
and so that’s that negative half there. 
Negative half is less than 0, and so it’s stable, and we knew that from before.
Are there any questions about that?</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-020.png" alt="" /></p>

<p>Cool. So that was one aspect that stability that is important to us in population modelling,
the stability of the dynamics of the model itself, of the population itself, 
at its different points in Y.</p>

<p>The second aspect is the stability of the numerical solution to the model. 
And so there’s similar concepts, but subtly different concepts here, and it can get a bit confusing.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-021.png" alt="" /></p>

<p>In your online lectures– I am going to abbreviate the notation now,
I’m going to write F of Y for DYDT –
and you would be given an initial value, YO, and you’re given a constant step size and time, 
which is H, and then you can just kind of churn through the maths. 
What you do is you get your YO … you put it into this equation and in here to calculate your Y1.
And then you substitute your Y1 in to get your Y2, and yada yada yada. And it’s really boring.
But what’s actually going on behind here? Something exciting.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-022.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_expon_growth_euler.m">plot_expon_growth_euler.m</a></p>

<p>Let’s take a concrete example, using, again, this awesome tool called Quiverplot.
So … Here I am using my abbreviated notation for this particular example. 
I’m going to start at Y equals 1, and we know that our analytic solution here is this curve.
So I’m going to take a step size of H equals 5. 
So what I’m saying is that I’m going to step forward in time by 5. 
So, just to make that … clear, I’m going to put a dotted line at the 5 …
and at the 10 … That’s what we’re heading towards.</p>

<p>Alright, so you have seen examples of how to do this mathematically,
here, we have the first two steps. So get Y0 equals 1 … we substitute it in, so there’s our 1. 
This here is our 5 from the H. 0.3 is that 0.3 up there. 
1 is because we’re at H … YO equals 1 … and that equals 2.5.
And then we can substitute that in to get the next one. 
So we put 2.5 in here and in here. This part here is the DYDT, and we get 6.25.</p>

<p>It doesn’t feel very meaningful. 
But we can understand what’s going on if we draw what the Euler method is doing visually.</p>

<p>So when we’re at YO, we’re starting here.
And we’re going to take a step to calculate our Y1. 
What we’re doing is we’re catching this arrow that’s pointing in this direction.
What we’re going to do is we’re going to say that’s the derivative of this 
function until we hit the wall at 5, so we’re going to project ourselves forward….
And here we are … that’s our Y1 at 2.5.</p>

<p>That’s what the Euler Method does.
It takes the derivative at the point you started at, 
and steps forward in time using that derivative to get the next value.</p>

<p>At this point here, we’ve got an arrow that kind of points in that direction.
And so we’re going to assume that that’s constant until our next point in time.
And so we get to this point here … and so if I’ve drawn it well, that should be 6.25.</p>

<p>You can imagine that if I took more sensibly small steps, I would have tracked the value a little closer.
But I took these big steps, because I wanted to show you the first issue with Euler’s method, 
which is that it’s not so great. So you can see here …
Take the distance from, say, here to here … this is a lot of error.
And you can imagine, as this red line goes up, the error’s just getting worse and worse.
That’s also true if I’d have taken smaller step sizes, 
that error would be getting worse and worse, but it’s easier to draw them large.</p>

<p>So you might be thinking, well,
maybe that’s just because this is, like, the exponential growth, 
and so maybe if you had some true … solution that sort of asymptotes that you would kind of …
the solution that you’re finding with Euler’s method will get closer and closer to the true value.
And that’s true, that is good intuition, and that is correct.</p>

<p>However, there’s other perverse things that can happen with the Euler’s method, 
even when you’ve got this asymptoting kind of situation. 
And so that brings us to the second aspect of stability, 
which is not the stability of the dynamics themselves, but the stability of the Euler method,
whether it converges on the steady state of the dynamics or not.</p>

<p>So let me make that concrete.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-023.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_logistic_euler.m">plot_logistic_euler.m</a></p>

<p>So, here I’m taking really small step sizes … H equals 1 … And there’s my first step, 
my second step, my third step.
The reason I’m able to do this is because I put a grey line in there to help myself later.
And you can see it nicely converges to the dynamical, stable steady state. 
So the numerical solution is smoothly converging to the steady state, 
and here we would say that the numerical solution is stable.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-024.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_logistic_euler.m">plot_logistic_euler.m</a></p>

<p>Let’s take slightly bigger step sizes. I’m going to use a step size of 3.5 this time.
So my first step takes me to here … My next step takes me to here …
The next step takes me to here, oh, wow, I bumped over the steady-state.
… then I bumped back … Boom, up, and down … 
But each bump, I’m getting closer and closer, 
until the bumps are kind of hard to see, and it basically looks like …</p>

<p>So, this numerical solution also converges to the dynamical steady state, 
but it does it in this oscillatory way. 
It goes above and below and above and below, switching, until … but it’s getting closer and closer. 
So we can say the numerical solution is also stable in this situation.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-025.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_logistic_euler.m">plot_logistic_euler.m</a></p>

<p>Let’s make our step size even bigger. Alright, 4.5.
So, take a step of 4.5, up to here …
The next step gets me to here … So it’s oscillating like it did before….
Something bad is happening …
Each time I oscillate, I catch one of those, those arrows that’s steep enough to send me 
further and further away.
And so this oscillates and goes away in its oscillations from the steady state.
And so this diverges from the dynamical steady state in an oscillatory way, 
and so we’d say that the numerical solution is unstable.</p>

<p>So note here, though, that the dynamics are stable. 
The dynamics of the population is stable. 
It’s the numerical solution that’s unstable. 
This is the two aspects I was talking about before, 
two aspects of stability that matter to us when we’re modelling, for instance, population growth.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-026.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_logistic_small_pert.m">plot_logistic_small_pert.m</a></p>

<p>Now, stability is usually defined in terms of the small perturbation. 
So we give our ball a tiny knock and see if it rolls down, right?
So let’s, let’s look at stability with respect to a small perturbation.</p>

<p>Here we’re doing H equals 4.5 steps. 
So, imagine that we’re coming along just fine here … so our Euler’s method, 
when it’s on that 8, will stay at that 8, because it’s always changing by 0.</p>

<p>Now we perturb it a little bit, and we’ll have an oscillation….
And with each step, our bounce gets higher and higher …
And so this one’s still diverging as well.</p>

<p>Before, I showed you us approaching the steady state, but really, 
we define stability with respect to a small perturbation near the steady state. 
You can see that, again, yes, it’s unstable, as we thought before.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-027.png" alt="" /></p>

<p>In your lecture notes, in your online lectures, 
there’s a slide about this, and it talks about a mapping, G, and if it has a fixed point, 
and the key quantity here is … 
if the absolute value of G-Z is less than 1, then Z is stable.</p>

<p>And so this is confusing, because we were talking about stability before, and we were looking for 
derivatve less than 0 and greater than 0, and now we’ve got an absolute value ..</p>

<p>What’s going on here? What is the meaning of what’s happening here?</p>

<p>I could leave you on a cliffhanger.
I’ve got fair bit of content to get through. Do you guys want a 5-minute break, or do you want to push through?
Short break. Short break, excellent idea.</p>

<p>(Break)</p>

<p>Alright, so where were we? So we’ve got this idea of stability of the numerical method.
And we have an example of the Euler’s method being unstable with respect to a small perturbation. 
And so, in our online lectures, we were told that we’re to take some derivative, 
find its absolute value, see that it’s less than 1, and that means it’s stable.</p>

<p>And we’re like, okay, but why?</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-028.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_cobweb_stable.m">plot_cobweb_stable.m</a></p>

<p>The second friend I’m going to introduce you to tonight is called the cobweb plot.
This is another visual tool, a bit like the quiver plot I showed you before, 
and it’s used for understanding something called a linear map.</p>

<p>So, you’ve seen this kind of thing before, where you have some value at the next step, 
and it’s equal to some function of the value at the previous step.
And here we’ve got some Y stars, and that happens to be the fixed point, 
so I might write that in there, actually … Fixed point.
Which means that if we start at Y star, then Y… n plus 1 will equal Y, N will equal Y star.
And this is our G that’s, defined in the online lectures.</p>

<p>So, how would we go about solving this kind of visually? 
What I’ve done here is I’ve plotted, the one-to-one line.
And here I’ve got YN, our previous step, and YN plus 1, our next step.
And this here is .. the function G, up the top there.
This is our fixed point where the two lines meet.</p>

<p>And so, how would we do this mechanically? 
So we might start at some Y0 value.
And then to get to Y1, we evaluate, G at Y0. And so, that corresponds …
to this point here. And so visually, we might …
just draw a line to indicate that, and that’s our Y1.
So the next step in this process is that we take
our Y1 value, and we substitute it into G again to get our Y2.
And so, we need to find … set our YN equal to Y …
Y1, we can actually kind of draw a line there, 
and you see where that touches the one-to-one point, is where our new Y1 is?
We can do a step to our next Y2, so we’ve marked that here….
And then we’re going to substitute our Y2 into G to get our Y3 …
Y2, you’re going to go up, and that gives us our Y3 ..</p>

<p>And so you can see that this, staircase thing that I’m drawing is basically keeping a track of our 
Y1, Y2, Y3, and so on, values, and … it staircases into this point.</p>

<p>We can do it from the other end, if we’ve started here.
We would go … Well, the point of these is usually to stop thinking, so you go …
Vert G … horizontal 1 … vert G … horizontal 1 … And so on.
So it’s a visual representation of using this iterative map.</p>

<p>Are there any questions about what I’ve drawn there? Does that make sense?
I’m not getting a yes or a no.
Okay, so the reason that I’m showing you this … yes, okay, great, thank you.
It sort of makes sense.</p>

<p>It seems a bit less useful than the quiver, yeah? 
That’s because I haven’t told you how it’s useful yet.</p>

<p>Alright, so the reason that it’s useful, why am I telling you about linear maps, 
is because it turns out that Euler’s method is also an iterated function. 
It’s the same kind of thing, check it out. 
You’ve got here … YN .. YN … YN plus 1.
It’s the same as before, YN over here … YN plus 1.
And so that means that we could actually be drawing Euler’s method using this same kind of technique, right?</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-029.png" alt="" /></p>

<p>In your online lectures, you’ve got the Euler’s method on a Logistic Equation.
So I’ve expanded it out here, just for convenience. 
And the first step is to write it as GY, so we’re replacing all these YNs with Ys.
This is our G.
And then you’re told that you take the derivative of G with respect to Y, evaluated at the steady state.</p>

<p>So in your online lectures, the notation used is this …
So, hang on, what even is this? It’s okay, it’s the slope of G,
it’s the slope of the Euler method at Y star.</p>

<p>So, if we were really, really close to our steady state, 
and we did a small perturbation,
then we could kind of approximate what our map looks like as a line instead of the curve, 
and assume that the slope stays the same, provided we make our perturbation small enough. 
And so that’s the principle of stability analysis, 
it’s always with respect to a small enough perturbation that our maths is easy.
And then we can use the slope of Y star.</p>

<p>So, let’s try applying this thing, and see if we can connect it back to the meaning.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-030.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_logistic_euler_red.m">plot_logistic_euler_red.m</a></p>

<p>This was our example before, where we had H equals 1, 
and it converged nicely and smoothly towards the steady state, the dynamical steady state.
And so, if we …</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-031.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_cobweb_stable.m">plot_cobweb_stable.m</a></p>

<p>… take that, DGDY that we calculated before, 
and we substitute in H equals 0.5 … so we put an 0.5 in here … 
then we end up with 0.5 as our slope. 
And in fact that is giving us the same line as before that I did the cobweb plot on.
I cheated, it’s the same example.</p>

<p>In this case, we know our Y star, it’s actually … equal to 8. 
And so what we do is we imagine ourselves perturbing our Euler’s method around this Y star just a little bit. 
So we start our perturbation here … And then we do … Just what we did before … visually.</p>

<p>And you see that even if we approach from above or below,
our Euler’s method values get closer and closer to our Y star,
until they’re kind of stuck there.
And so this is our Euler’s method converging towards that dynamical steady state.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-032.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_logistic_euler_red.m">plot_logistic_euler_red.m</a></p>

<p>Let’s take a look at the next example we had. 
This was when H equals 3.5, and it did converge, but it converged in this oscillatory way.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-033.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_cobweb_osc_stable.m">plot_cobweb_osc_stable.m</a></p>

<p>So let’s use the derivative of G that we calculated before. 
We’re going to sub in here, this time, H equals 3.5 … so we put an 3.5 in here … and we get negative 0.75. 
So here’s our cobweb plot that we’re about to plot with the G slope at negative 0.75.</p>

<p>And so, if we do our iterations on this … so this here is, as before, Y star…
Actually, I want to make that a little bit further down so that I’ll have space …
And it’s the same on this side.
So let’s say we start with our perturbation here at Y0, So we go …
Up to here, this is our first Euler step, Y1.
We’ll draw it on both axes.
This is our next one, Y2 … Drew it on the other axis as well.
That’s our Y3 … on axis as well.
There’s our Y4 … Y5 … Y5 .. And so on.</p>

<p>Check it out. Looks like … Y0 was here…
… Then for two, we jump to the other side. For three, we jump to the … of 1, I should say. 
For two, we jump to the other side, but a little bit closer. … 3, we jump to the other side, 
but a little bit closer …</p>

<p>It’s oscillating towards the steady-state.
Which is exactly what we did when we looked at the quiver plots, right? It’s captured that dynamic for us.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-034.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_logistic_euler_red.m">plot_logistic_euler_red.m</a></p>

<p>Let’s do one more example. So this was the one with H equals 4.5, and this one was unstable. 
It was oscillatory, and it diverged.</p>

<p>You think the cobweb plot looks cool? Yeah, same.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-035.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_cobweb_unstable.m">plot_cobweb_unstable.m</a></p>

<p>So again, it’s the same thing. So we get … Our H equals 4.5, 
and we put it into our derivative here, and we’ve got negative 1.25, 
and so we’re drawing that line with a slope of negative 1.25.</p>

<p>This is our … Y star equals 8. Same thing here.
This time, I’m going to start close.
Because if I do the same process before …
Horizontal 1, vertical blue, horizontal 1, Vertical G, horizontal 1, vertical G.
You can see … there’s this same oscillatory thing happening,
but the oscillations are getting bigger and bigger …
And then it shoots off.</p>

<p>And so this cobweb plot and the pictures we’re drawing, 
is capturing what was happening in the Euler, in the numerical solutions dynamics.</p>

<p>At this point, are there any questions about what I’m doing there?</p>

<p>(A bit of back and forth with students)</p>

<p>… You all understand it really well. That’s good, because now you’re going to do your own.</p>

<p><img src="/wp-content/uploads/2025/12/interactive_lecture_MXB261-036.png" alt="" /></p>

<p>Figure scripts: <a href="/wp-content/uploads/2025/12/plot_cobweb_unstable.m">plot_cobweb_unstable.m</a>,
<a href="/wp-content/uploads/2025/12/plot_cobweb_neg1.m">plot_cobweb_neg1.m</a>,
<a href="/wp-content/uploads/2025/12/plot_cobweb_osc_stable.m">plot_cobweb_osc_stable.m</a>,
<a href="/wp-content/uploads/2025/12/plot_cobweb_stable.m">plot_cobweb_stable.m</a>,
<a href="/wp-content/uploads/2025/12/plot_cobweb_pos1.m">plot_cobweb_pos1.m</a>,
<a href="/wp-content/uploads/2025/12/plot_cobweb_gt1.m">plot_cobweb_gt1.m</a></p>

<p>Okay, I’m going to send you to a pair-and-share. 
I’m going to send you guys to a breakout room.</p>

<p>I have already done … This one … and this one … and this one.
I want you guys to do this one…. This one … And this one. 
This one’s the important one.</p>

<p>While you’re in the breakout room. 
Think about what the cobweb plot would look like, 
and what that means for the dynamics of the numerical solution near whatever steady state this is representing.
And while you’re there, I’ll draw my own back in there as well, so that you’ll have something to look
at how to do it.
I’m gonna send you all away.</p>

<p>(Pair and share in class, breakout rooms online)</p>

<p>Okay, cool. 
Alright, so let’s go through them one by one.
So what happens in this one here? If I … start off here, what did you guys notice about the …
the cobweb plot that you end up with for this one. 
Comes back to where it started. Did you guys in chat find the same thing?</p>

<p>Okay, so we had a question about what happens in the cobweb plot with this one, 
and someone in the room said that it comes back to itself, and so that is indeed the case, 
that we end up going round.
And, I have to make sure my lines are straight, and then we’re just going around in a circle, like this.</p>

<p>Alright, what about this one here, where G … the derivative of G equals 1? 
What happens with the dynamics in this one? …
Yeah, like, not a lot, hey. 
So if you start here, there’s a line underneath… Are you just kind of stuck there?
But they have the same intuition. 
Would it be a series of boxes inside each other for each initial value?
Yeah, it’d just be a point, because it’s just stuck there for each initial value.
This is like a weird edge case on the one. 
We’re going to return to this point of it being the one.</p>

<p>How about this one? The final one.</p>

<p>The top center graph? Oh, I’m sorry. 
Oh, yes, yes, you were right. Exactly true, that you would … if you started here,
you would have another little limit cycle … going around like this. 
All the way around.
Yeah, sorry, I thought you meant the one that I was talking about, the bottom center.
That’s exactly right. Thanks.</p>

<p>And how about the final one? What does the dynamics look like for this one? Maybe if we start from here,
what did you guys find?
… Chat, you can beat them if you’re quick.
… We figured it would go across to the right, then up, and repeat.
Across to the right. 
No, see, yeah, so this is the only trick to the cobweb plot, 
is that you’ve got to remember that if you’re starting at the 1 to 1 line, 
then you’ve got to go vertical first.
So you’re actually gonna go down like this.
Because I was drawing the others on the left-hand side, wasn’t I? 
I mean, I drew it on the right-hand side here, but you’d basically have to do that same pattern here.
So instead, you’d be going vertical down, and then across to the left.
And then vertical down.
And across to the left.
And so, in this case, your Euler solution, so it always starts from that diagonal bisector. 
… Yeah, yeah, so the reason is because, remember when I was talking about it before?
I said that this is my Y0…. Y0 here … And this was my Y1.
And so I represented that by the vertical line from the one to the … to the G….
Yep.</p>

<p>Alright, cool, so we’ve kind of looked at the space of all of the possible slopes of G, And …
We can say that this one is unstable … Numerical solution, the Euler method is unstable. 
This one is stable … it oscillates inwards. This one is stable … It converges inwards smoothly.
And this one here is unstable.</p>

<p>So the thing that’s separating them, turns out, it’s these very edge cases here,
when the derivative of G With respect to Y is negative 1,
when the derivative of G with respect to Y is 1.</p>

<p>And so now, you remember that your online lectures, there was this rule about the G Z  …
Less … absolute value … less than 1. 
And now you know where that 1 is coming from! 
That’s why 1 separates them out.
That’s the meaning of the stability of the fixed point and the Euler solution.</p>

<p>There’s a Menti quiz on the screen right now, 
and I’d be really grateful if you could just scan it into your phones or something and take the quiz. 
The reason is that this course is under development, and I get some input into it. 
And so basically, it’s just asking you, have you seen this stuff before?
Yes or no, and was it useful to you anyway, or if it was new to you, was it useful to you in general?</p>

<p>While we’re doing that, I’ll just talk about where we’ve been. 
We looked at the two ways that stability is relevant to population modelling.</p>

<p>The first way is the stability of the dynamics of the model itself. 
So, is a population at a steady state? Is that steady state for the population stable?</p>

<p>But then there’s also another layer that we put on top of it, which is our numerical method. 
We’re looking at Euler’s method only so far, 
but you will be looking at other methods in later online lectures.
And all methods, eventually, are variations on Euler method, they build on this same principle, 
that you’re taking the derivative, you’re calculating the derivative and point, 
and you’re making some kind of jump.</p>

<p>You might be clever about it.
You might take the average of derivatives over a space, or you might, go, oh, this derivative is too high, 
I’m going to take a smaller step at this point. But it’s all Euler methods at the bottom,
all the way down.</p>

<p>And so we need to know about the stability of that numerical solution when it’s near the steady state, 
because, for instance, we might be interested in the step size. 
But then also later in the lectures, we leave this kind of iterative map to some really cool dynamics, 
like chaotic dynamics, period doubling chaos, which you’ll get a chance to look at in your practical.</p>

<p>That’s the end of that part, and now it’s time for activities…</p>]]></content><author><name>nadiah_kristensen</name></author><category term="teaching" /><summary type="html"><![CDATA[This semester, I had the opportunity to do 7 weeks of “interactive lectures” at QUT for MXB261 Modelling and Simulation Science, as well as one guest lecture on evolutionary game theory. In this blog post, I’ll talk about my experiences, share the first half of one of the lectures I gave, and provide the code, to generate the figures, in case any of that is useful to anyone.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://nadiah.org/wp-content/uploads/2025/12/interactive_lecture_MXB261-008.png" /><media:content medium="image" url="https://nadiah.org/wp-content/uploads/2025/12/interactive_lecture_MXB261-008.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Analytic solution for the stationary distribution in an iterated game with implementation errors</title><link href="https://nadiah.org/2025/11/18/analytic-stationary" rel="alternate" type="text/html" title="Analytic solution for the stationary distribution in an iterated game with implementation errors" /><published>2025-11-18T04:44:54+00:00</published><updated>2025-11-18T04:44:54+00:00</updated><id>https://nadiah.org/2025/11/18/analytic-stationary</id><content type="html" xml:base="https://nadiah.org/2025/11/18/analytic-stationary"><![CDATA[<p>For the past year, 
I’ve been working in my spare time through a very interesting paper by 
<a href="https://www.nature.com/articles/s41467-023-39625-9.pdf">Kleshnina et al. (2023)</a>.
The paper concerns an evolutionary game theory model
where individuals play an iterated Prisoner’s Dilemma with an environmental feedback.
In a <a href="https://nadiah.org/2024/11/20/kleshnina_2023">previous post</a>,
I experimented with a method to automate the identification of the subgame-perfect Nash equilibria
and their parameter-value conditions.</p>

<p>In this post,
I’ll show how the stationary distribution of actions taken during a repeated interaction can be found 
analytically and solved algorithmically using <a href="https://www.sympy.org/en/index.html">SymPy</a>.
The typical approach requires choosing a specific value for \(\varepsilon\), 
which is the probability that players make implementation errors. 
However, this choice is somewhat arbitrary if \(\varepsilon\) is introduced purely as a mathematical device to make the model tractable. 
My method avoids this arbitrariness by finding the limiting stationary distribution as \(\varepsilon \rightarrow 0\).</p>

<p>Kleshnina et al.’s paper concerns pairwise interactions that repeat over a number of rounds.
The action that each player takes in each round is governed by their strategy,
and it is a function of the actions in the previous round and the environmental state.
If we assume that the probability \(\delta\) that the game continues for another round is high,
then the payoff each strategy receives from interacting with each other strategy can be approximated.
The approximation is made based on the stationary distribution of environment+action states 
when those two strategies interact.</p>

<p>To find the stationary distribution, 
Kleshnina et al. (2023) used a standard procedure,
which is an approximate method.
The Markov chain describing the sequence of environment+action states is not necessarily irreducible
(e.g., Fig. 1).
This means there are some states \(i\) that cannot reach another state \(j\) no 
matter how many rounds are played.
Therefore, to find the stationary distribution, 
they assumed that in each round each player will make a mistake with probability \(\varepsilon\),
playing cooperate instead of defect or vice versa.
They set \(\varepsilon\) to a small fixed value,
in their case \(\varepsilon = 0.01\),
and modified the transition matrix accordingly.
Then they found the left-eigenvector of the transition matrix numerically,
which gives the stationary distribution.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2024/11/cycle_example.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2024/11/cycle_example.png" alt="**Figure 1**. An example scenario that is not irreducible, where 'good' and 'bad' refer to the environment state, and the node labels refer to the players' actions, 'C' for cooperate and 'D' for defect. The arrows indicate the transitions between environment+action states. For example, if the first round is in a good environment and Player 1 defects and Player 2 cooperates (DC), then the next round will also be in a good environment, and both players will defect. This scenario is reducible because some states cannot be reached from others. For example, the state good-CC can neither reach nor be reached from any other state." />
    </a>
    
    <figcaption><span><p><strong>Figure 1</strong>. An example scenario that is not irreducible, where ‘good’ and ‘bad’ refer to the environment state, and the node labels refer to the players’ actions, ‘C’ for cooperate and ‘D’ for defect. The arrows indicate the transitions between environment+action states. For example, if the first round is in a good environment and Player 1 defects and Player 2 cooperates (DC), then the next round will also be in a good environment, and both players will defect. This scenario is reducible because some states cannot be reached from others. For example, the state good-CC can neither reach nor be reached from any other state.</p>
</span></figcaption>
</figure>

<p>Once all the stationary distributions between all pairs of strategies are found,
the evolutionary dynamics can be solved.
Each stationary distribution determines the payoff that both strategies 
receive when interacting with each other. 
These payoffs are then input to the evolutionary model, 
which describes how the population moves from fixation of one strategy to another over time.
It is also a Markov chain model,
and its stationary distribution gives the evolutionary steady state.</p>

<p>Fig. 2 below reproduces Fig. 3a from their paper,
which shows the proportion of time selected strategies are at fixation at the evolutionary steady state.
Some nodes in the figure are labelled with a binary string,
which is a code that defines that strategy,
while WSLS refers to the “win-stay lose-shift” strategy,
and ALLD refers to “always defect”.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2025/11/Kleshnina_Fig3a.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2025/11/Kleshnina_Fig3a.png" alt="**Figure 2**. Adapted from Fig. 3a from Kleshnina et al. (2023), calculated for $$\varepsilon = 0.01$$. The nodes are selected strategies, and the percentages are the proportion of time in the long-term that the population spends with that strategy at fixation." />
    </a>
    
    <figcaption><span><p><strong>Figure 2</strong>. Adapted from Fig. 3a from Kleshnina et al. (2023), calculated for \(\varepsilon = 0.01\). The nodes are selected strategies, and the percentages are the proportion of time in the long-term that the population spends with that strategy at fixation.</p>
</span></figcaption>
</figure>

<p>The strategy proportions in Fig. 2 were calculated based on \(\varepsilon = 0.01\),
and those proportions will change when \(\varepsilon\) changes (Fig. 3).
In particular,
as \(\varepsilon\) approaches zero,
the proportion of time the population that pursues the most frequent strategy almost halves,
and the relative ordering of the time proportions changes as well.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2025/11/eps_v_stationary_distn.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2025/11/eps_v_stationary_distn.png" alt="**Figure 3**. How the proportion of time spent at each strategy in Fig. 2 varies as a function of $$\varepsilon$$. Note that the purple line that seems to correspond to ALLD in Fig. 2 is in fact strategy $$\boldsymbol{p} = (0, 0, 0, 0; -, 0, 0, 0)$$, where the hyphen refers to a wildcard (can be 0 for defect or 1 for cooperate). ALLD, which has strategy $$\boldsymbol{p} = (0, 0, 0, 0; 0, 0, 0, 0)$$, has half the proportion of the purple line shown. I couldn't figure out the reason for the discrepancy." />
    </a>
    
    <figcaption><span><p><strong>Figure 3</strong>. How the proportion of time spent at each strategy in Fig. 2 varies as a function of \(\varepsilon\). Note that the purple line that seems to correspond to ALLD in Fig. 2 is in fact strategy \(\boldsymbol{p} = (0, 0, 0, 0; -, 0, 0, 0)\), where the hyphen refers to a wildcard (can be 0 for defect or 1 for cooperate). ALLD, which has strategy \(\boldsymbol{p} = (0, 0, 0, 0; 0, 0, 0, 0)\), has half the proportion of the purple line shown. I couldn’t figure out the reason for the discrepancy.</p>
</span></figcaption>
</figure>

<p>Setting \(\varepsilon\) to some specific value may be valid depending on the question being asked.
Nonetheless,
in situations where \(\varepsilon\) is simply intended to represent “some small chance”
of a mistake, 
then an intuitively attractive choice would be to find the solution as \(\varepsilon\) approaches zero.
But that’s not something that’s easy to solve using numerical methods.
It’s not obvious how close to \(\varepsilon = 0\) is “close enough”,
and exploring the effects of different parameter values on the model is already computationally expensive.
Furthermore,
the smaller the value chosen for \(\varepsilon\) is, the more likely we are to run into numerical issues.</p>

<p>Instead,
in the sections below,
I detail how the stationary distribution as \(\varepsilon \rightarrow 0\)
can be found analytically.
In general, 
when the stationary distributions are found analytically,
the evolutionary component of the model can predict quite different
strategy proportions (Fig. 4).
This also means that any conclusions we reach about which strategies are favoured by natural selection 
can depend on the method used.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2025/11/compare_analytic_approx.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2025/11/compare_analytic_approx.png" alt="**Figure 4**. Comparing the evolutionary steady state when the analytic and approximate methods are used. Only the top 12 strategies are shown. Strategies are grouped into payoff-equivalent pairs, where a hyphen indicates that either cooperate or defect can be played." />
    </a>
    
    <figcaption><span><p><strong>Figure 4</strong>. Comparing the evolutionary steady state when the analytic and approximate methods are used. Only the top 12 strategies are shown. Strategies are grouped into payoff-equivalent pairs, where a hyphen indicates that either cooperate or defect can be played.</p>
</span></figcaption>
</figure>

<p>The underlying principle to the analytic solution is illustrated in Example 1 below.
Example 1 describes a somewhat different Markov chain scenario from the type they modelled, which I chose for its clarity.
The section below works through a real example from their model,
both by hand and in Python using <a href="https://www.sympy.org/en/index.html">SymPy</a>.</p>

<div class="content-highlight">

<p><strong>Example 1.</strong> 
Consider a Markov chain with transition matrix</p>

\[\begin{equation*}
    \boldsymbol{P} = 
    \begin{pmatrix}
        1 - \varepsilon &amp; \varepsilon \\
        \varepsilon &amp; 1 - \varepsilon \\
    \end{pmatrix},
\end{equation*}\]

<p>where \(\varepsilon\) is small.
Show that, as \(\varepsilon \rightarrow 0\),
the stationary distribution approaches</p>

\[\boldsymbol{v}_{\varepsilon \rightarrow 0} \equiv \lim_{\varepsilon \rightarrow 0} \boldsymbol{v}
= (1/2, 1/2).\]

\[\star\]

<p>The stationary distribution \(\boldsymbol{v} = (v_1, v_2)\) satisfies</p>

\[\begin{equation}
    \boldsymbol{v} = \boldsymbol{v} \; \boldsymbol{P}.
    \label{v=vP}
    \tag{1}
\end{equation}\]

<p>We can write the elements of \(\boldsymbol{v}\) as polynomials of \(\varepsilon\)</p>

\[\begin{align}
    v_1 &amp;= k_{1, 0} + k_{1, 1} \varepsilon + k_{1, 2} \varepsilon^2 + \ldots \nonumber \\
    v_2 &amp;= k_{2, 0} + k_{2, 1} \varepsilon + k_{2, 2} \varepsilon^2 + \ldots \label{v0v1} \tag{2}
\end{align}\]

<p>where \(k_{i,j}\) is the coefficient 
in the polynomial for state \(i\)
of the \(j\)-th power of \(\varepsilon\).
Then, as \(\varepsilon \rightarrow 0\),
the stationary distribution will approach</p>

\[\boldsymbol{v}_{\varepsilon \rightarrow 0} = (k_{1, 0}, k_{2, 0}).\]

<p>Our coefficients of interest represent probabilities,
which gives us 
one equation describing the relationship between them</p>

\[\begin{equation}
    1 = k_{1, 0} + k_{2, 0}.
    \label{normalisation}
    \tag{3}
\end{equation}\]

<p>To obtain more relationships,
we can substitute 
Eq. \ref{v0v1} into Eq. \ref{v=vP} and equate matching-\(\varepsilon\)-order terms.
We will increase the order until the coefficients defining 
\(\boldsymbol{v}_{\varepsilon \rightarrow 0}\) can be solved.</p>

<p>Substituting Eq. \ref{v0v1} into Eq. \ref{v=vP}</p>

\[\begin{align*}
    v_1
    &amp;= \varepsilon (k_{2, 0} + k_{2, 1} \varepsilon + \ldots)
        + (1 - \varepsilon)(k_{1, 0} + k_{1, 1} \varepsilon + \ldots) \\
    v_2
    &amp;= \varepsilon (k_{1, 0} + k_{1, 1} \varepsilon + \ldots)
        + (1 - \varepsilon)(k_{2, 0} + k_{2, 1} \varepsilon + \ldots) 
\end{align*}\]

<p>Substituting Eq. \ref{v0v1} on the left-hand side as well and expanding the right-hand side</p>

\[\begin{align}
    k_{1, 0} + k_{1, 1} \varepsilon + k_{1, 2} \varepsilon^2 + \ldots
    &amp;= k_{1,0} + \varepsilon(k_{2,0} -k_{1,0} + k_{1,1})
    + \varepsilon^2 (k_{2,1} -k_{1,1} + \ldots) + \ldots \\
    k_{2, 0} + k_{2, 1} \varepsilon + k_{2, 2} \varepsilon^2 + \ldots
    &amp;= k_{2,0} + \varepsilon(k_{1,0} - k_{2,0} + k_{2,1})
    + \varepsilon^2 (k_{1,1} - k_{2,1} + \ldots) + \ldots \label{expanded} \tag{4}
\end{align}\]

<p>Matching the \(\varepsilon^0\) terms in Eq. \ref{expanded} gives us no useful information,
so we increase the epsilon order.
Matching the \(\varepsilon^1\) terms and simplifying,
we obtain one new equation</p>

\[\begin{equation}
    0 = k_{1, 0} - k_{2, 0}. \label{eps1} \tag{5}
\end{equation}\]

<p>We now have two linear equations to solve for two unknowns.
Solving Eq. \ref{normalisation} and \ref{eps1} simultaneously,
we obtain</p>

\[\begin{align*}
    k_{1, 0} &amp;= 1/2, \\
    k_{2, 0} &amp;= 1/2,
\end{align*}\]

<p>and therefore</p>

\[\begin{equation*}
    \boldsymbol{v}_{\varepsilon \rightarrow 0} = (1/2, 1/2).
\end{equation*}\]


</div>

<h3 id="worked-example">Worked example</h3>

<p>Consider a game played in the timeout environment</p>

\[\boldsymbol{q} = ((1, 0, 0), (1, 1, 1))\]

<p>between two players,
an All-Defect strategist</p>

\[\boldsymbol{p}_0 = ((0, 0, 0, 0), (0, 0, 0, 0))\]

<p>and a peculiar,
mostly-defecting strategy who only cooperates
when the previous action was DD</p>

\[\boldsymbol{p}_1 = ((0, 0, 0, 1), (0, 0, 0, 1)).\]

<h4 id="worked-example-by-hand">Worked example by hand</h4>

<p>Index the states:
(1) gCC, (2) gCD, (3) gDC, (4) gDD, (5) bCC, (6) bCD, (7) bDC, (8) bDD.</p>

<p>In a deterministic game played without any errors,
the transition matrix is</p>

\[\begin{equation*}
    T = 
    \begin{pmatrix}
      0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\
       0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 \\
       0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 \\
       0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 \\
       0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\
       0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\
       0 &amp; 0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\
       0 &amp; 0 &amp; 1 &amp; 0 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\
    \end{pmatrix}
\end{equation*}\]

<p>where rows are predecessors, columns are successors,
and the state vector is multiplied on left.</p>

<p>The dynamics has two attractors
— \(\{ \text{gDD}, \text{bDC} \}\) and \(\{ \text{bDD}, \text{gDC} \}\) —
and the Markov chain is not irreducible (Fig. 5a).</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2025/11/worked_example.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2025/11/worked_example.png" alt="**Figure 5**. Environment+action state transition graphs for the current example. The environment is indicated by the border: a plain border is a good environment, and a double-ring red border bad. Actions are indicated by the text inside each node, with Player 1's action first. (a) Transition graph when no action mistakes are made. This Markov chain is not irreducible. (b) Transition graph when errors are permitted, which are shown in grey. This chain is irreducible, and so the stationary distribution can be found. For clarity, only errors that are made in an attractor state are shown." />
    </a>
    
    <figcaption><span><p><strong>Figure 5</strong>. Environment+action state transition graphs for the current example. The environment is indicated by the border: a plain border is a good environment, and a double-ring red border bad. Actions are indicated by the text inside each node, with Player 1’s action first. (a) Transition graph when no action mistakes are made. This Markov chain is not irreducible. (b) Transition graph when errors are permitted, which are shown in grey. This chain is irreducible, and so the stationary distribution can be found. For clarity, only errors that are made in an attractor state are shown.</p>
</span></figcaption>
</figure>

<p>If players can make errors,
then the Markov chain becomes irreducible (Fig. 5b).
The transition matrix with the errors included is</p>

\[\begin{equation*}
    P = 
    \begin{pmatrix}
      \varepsilon^2 &amp; \varepsilon(1 - \varepsilon) &amp; \varepsilon(1 - \varepsilon) &amp; (1 - \varepsilon)^2 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\
       0 &amp; 0 &amp; 0 &amp; 0 &amp; \varepsilon^2 &amp; \varepsilon(1 - \varepsilon) &amp; \varepsilon(1 - \varepsilon) &amp; (1 - \varepsilon)^2 \\
       0 &amp; 0 &amp; 0 &amp; 0 &amp; \varepsilon^2 &amp; \varepsilon(1 - \varepsilon) &amp; \varepsilon(1 - \varepsilon) &amp; (1 - \varepsilon)^2 \\
       0 &amp; 0 &amp; 0 &amp; 0 &amp; \varepsilon(1 - \varepsilon) &amp; \varepsilon^2 &amp; (1 - \varepsilon)^2 &amp; \varepsilon(1 - \varepsilon) \\
       \varepsilon^2 &amp; \varepsilon(1 - \varepsilon) &amp; \varepsilon(1 - \varepsilon) &amp; (1 - \varepsilon)^2 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\
       \varepsilon^2 &amp; \varepsilon(1 - \varepsilon) &amp; \varepsilon(1 - \varepsilon) &amp; (1 - \varepsilon)^2 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\
       \varepsilon^2 &amp; \varepsilon(1 - \varepsilon) &amp; \varepsilon(1 - \varepsilon) &amp; (1 - \varepsilon)^2 &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\
       \varepsilon(1 - \varepsilon) &amp; \varepsilon^2 &amp; (1 - \varepsilon)^2 &amp; \varepsilon(1 - \varepsilon) &amp; 0 &amp; 0 &amp; 0 &amp; 0 \\
    \end{pmatrix}
\end{equation*}\]

<p>For example, the \((1 - \varepsilon)^2\) in row \(i = 1\) (gCC) and column \(j = 4\) (gDD)
means that the game will transition from gCC to gDD in the event
that neither player makes an error, which occurs with probability \((1 - \varepsilon)^2\).</p>

<p>We can write the stationary distribution as a vector of polynomial functions of \(\varepsilon\)</p>

\[\begin{equation}
    \boldsymbol{v}^T = 
    \begin{pmatrix} v_1 \\ v_2 \\ \vdots \\ v_8 \\ \end{pmatrix}
    =
    \begin{pmatrix}
        k_{1, 0} + k_{1, 1} \varepsilon + k_{1, 2} \varepsilon^2 + k_{1, 3} \varepsilon^3 + \ldots \\
        k_{2, 0} + k_{2, 1} \varepsilon + k_{2, 2} \varepsilon^2 + k_{2, 3} \varepsilon^3 + \ldots \\
        \vdots \\
        k_{8, 0} + k_{8, 1} \varepsilon + k_{8, 2} \varepsilon^2 + k_{8, 3} \varepsilon^3 + \ldots \\
    \end{pmatrix}
    \label{Eq:stationary_distn_defn_v2}
    \tag{1}
\end{equation}\]

<p>where \(k_{i,j}\) is the coefficient of the \(j\)-th power of \(\varepsilon\) 
in the polynomial describing
the stationary distribution of state \(i\).
As \(\varepsilon \rightarrow 0\), 
\(\boldsymbol{v} \rightarrow \boldsymbol{v}_{\varepsilon \rightarrow 0} 
= (k_{1,0}, k_{2,0}, \ldots, k_{8,0})\),
so we are interested in finding the \(k_{i,0}\) coefficients.</p>

<p>The normalisation for the stationary distribution
gives our first equation</p>

\[\begin{equation}
    1 = k_{1,0} + k_{2, 0} + \ldots + k_{8,0}.
    \label{Eq:normalised_k_0_to_7_v2}
    \tag{2}
\end{equation}\]

<p>From Eq. \ref{Eq:stationary_distn_defn_v2},
we obtain 8 more equations</p>

\[\begin{align*}
    k_{1, 0} + k_{1, 1} \varepsilon + \ldots &amp; = 
    \varepsilon^2 (\varepsilon k_{1,1} + k_{1,0}) + \varepsilon^2 (\varepsilon k_{5,1} + k_{5,0}) 
    + \varepsilon^2 (\varepsilon k_{6,1} + k_{6,0}) + \varepsilon^2 (\varepsilon k_{7,1} + k_{7,0}) \\
    &amp; \quad + \varepsilon (1 - \varepsilon) (\varepsilon k_{8,1} + k_{8,0}) + \ldots \\
    k_{2, 0} + k_{2, 1} \varepsilon + \ldots &amp; = 
    \varepsilon^2 (\varepsilon k_{8,1} + k_{8,0}) + \varepsilon (1 - \varepsilon) (\varepsilon k_{1,1} + k_{1,0}) 
    + \varepsilon (1 - \varepsilon) (\varepsilon k_{5,1} + k_{5,0}) \\ 
    &amp; \quad + \varepsilon (1 - \varepsilon) (\varepsilon k_{6,1} + k_{6,0}) + \varepsilon (1 - \varepsilon) (\varepsilon k_{7,1} + k_{7,0}) + \ldots \\
    &amp; \vdots \\
    k_{8, 0} + k_{8, 1} \varepsilon + \ldots &amp; = 
    \varepsilon (1 - \varepsilon) (\varepsilon k_{4,1} + k_{4,0}) + (1 - \varepsilon)^2 (\varepsilon k_{2,1} + k_{2,0}) + (1 - \varepsilon)^2 (\varepsilon k_{3,1} + k_{3,0}) + \ldots
\end{align*}\]

<p>Grouping \(\varepsilon^0\) coefficients,
we obtain 8 equations:</p>

\[\begin{align*}
    k_{1,0} &amp;= 0, \\
    k_{2,0} &amp;= 0, \\
    k_{3,0} &amp;= k_{8,0}, \\
    k_{4,0} &amp;= k_{1,0} + k_{5,0} + k_{6,0} + k_{7,0}, \\
    k_{5,0} &amp;= 0, \\
    k_{6,0} &amp;= 0, \\
    k_{7,0} &amp;= k_{4,0}, \\
    k_{8,0} &amp;= k_{2,0} + k_{3,0},
\end{align*}\]

<p>which, 
combined with the normalisation constraint 
(Eq.~\ref{Eq:normalised_k_0_to_7_v2}),
can be reduced to the partial solution</p>

\[\begin{align}
    \begin{split}
    k_{1,0} &amp;= 0, \\
    k_{2,0} &amp;= 0, \\
    k_{3,0} &amp;= k_{8,0}, \\
    k_{4,0} &amp;= 1/2 - k_{8, 0}, \\
    k_{5,0} &amp;= 0, \\
    k_{6,0} &amp;= 0, \\
    k_{7,0} &amp;= 1/2 - k_{8, 0}.
    \end{split}
    \label{Eq:eps0_partial}
    \tag{3}
\end{align}\]

<p>An unknown in \(\boldsymbol{v}_{\varepsilon \rightarrow 0}\) remains,
so we consider the coefficients of the next power of \(\varepsilon\),
which is \(\varepsilon^1\).</p>

<p>Grouping \(\varepsilon^1\) coefficients,
we obtain 8 more equations</p>

\[\begin{align}
    \begin{split}
    k_{1,1} &amp;= k_{8,0}, \\
    k_{2,1} &amp;= k_{1,0} + k_{5,0} + k_{6,0} + k_{7,0}, \\
    k_{3,1} &amp;= k_{1,0} + k_{5,0} + k_{6,0} + k_{7,0} - 2 k_{8,0} + k_{8,1}, \\
    k_{4,1} &amp;= -2 k_{1,0} + k_{1,1} - 2 k_{5,0} + k_{5,1} - 2 k_{6,0} + k_{6,1} - 2 k_{7,0} + k_{7,1} + k_{8,0}, \\
    k_{5,1} &amp;= k_{4,0}, \\
    k_{6,1} &amp;= k_{2,0} + k_{3,0}, \\
    k_{7,1} &amp;= k_{2,0} + k_{3,0} - 2 k_{4,0} + k_{4,1}, \\
    k_{8,1} &amp;= -2 k_{2,0} + k_{2,1} - 2 k_{3,0} + k_{3,1} + k_{4,0}
    \end{split}
    \label{Eq:eps1}
    \tag{4}
\end{align}\]

<p>Substituting 
Eqs. \ref{Eq:eps0_partial} into Eqs. \ref{Eq:eps1},
we obtain the remaining coefficients</p>

\[\begin{align}
    \begin{split}
        k_{3,0} &amp;= 3/14, \\
        k_{4,0} &amp;= 2/7, \\
        k_{7,0} &amp;= 2/7, \\
        k_{8,0} &amp;= 3/14.
    \end{split}
\end{align}\]

<p>Therefore,
as \(\varepsilon \rightarrow 0\),
the stationary distribution approaches</p>

\[\begin{equation}
    \boldsymbol{v}_{\varepsilon \rightarrow 0} = ( 0, 0, 3/14, 2/7, 0, 0, 2/7, 3/14).
\end{equation}\]

<h4 id="worked-example-by-code">Worked example by code</h4>

<p>Begin by defining the problem:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># A simplified script that calculates the solution for the worked example. 
#
# The example has:
#   - q = (1, 0, 0; 1, 1, 1)
#   - p0 = (0, 0, 0, 0; 0, 0, 0, 0), ID = 0
#   - p1 = (0, 0, 0, 1; 0, 0, 0, 1), ID = 17
</span>
<span class="kn">import</span> <span class="nn">sympy</span> <span class="k">as</span> <span class="n">sp</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>


<span class="c1"># parameters
# ---
</span>
<span class="c1"># epsilon is the action-error probability defined as a symbolic variable
</span><span class="n">eps</span> <span class="o">=</span> <span class="n">sp</span><span class="p">.</span><span class="n">symbols</span><span class="p">(</span><span class="s">"eps"</span><span class="p">)</span>

<span class="c1"># we will try to solve by matching coefficients of epsilon powers up to eps^2
</span><span class="n">max_pwr</span> <span class="o">=</span> <span class="mi">2</span>

<span class="c1"># the 8 x 8 transition matrix between states including errors
# (normally generated by other code, but hardcoded here for clarity)
</span><span class="n">P</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">(</span>
    <span class="p">[</span>
        <span class="p">[</span><span class="n">eps</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
        <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">eps</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="p">],</span>
        <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">eps</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="p">],</span>
        <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="n">eps</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">)],</span>
        <span class="p">[</span><span class="n">eps</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
        <span class="p">[</span><span class="n">eps</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
        <span class="p">[</span><span class="n">eps</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span>
        <span class="p">[</span><span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="n">eps</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="p">,</span> <span class="n">eps</span><span class="o">*</span><span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">eps</span><span class="p">),</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span>
    <span class="p">],</span>
    <span class="n">dtype</span><span class="o">=</span><span class="nb">object</span>
<span class="p">)</span>
</code></pre></div></div>

<p>The first step to finding the stationary distribution is to define all our 
coefficients as symbolic variables</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># find the stationary distribution 
# ---
</span>
<span class="n">nbr_states</span> <span class="o">=</span> <span class="n">P</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>

<span class="c1"># matrix of epsilon coefficients, k_ij
#   - rows i correspond to the state
#   - columns j correspond to the power of epsilon
</span><span class="n">coeffs</span> <span class="o">=</span> <span class="p">[</span>
    <span class="p">[</span><span class="n">sp</span><span class="p">.</span><span class="n">symbols</span><span class="p">(</span><span class="sa">f</span><span class="s">"k</span><span class="si">{</span><span class="n">state_idx</span><span class="si">}{</span><span class="n">pwr</span><span class="si">}</span><span class="s">"</span><span class="p">)</span> <span class="k">for</span> <span class="n">pwr</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">max_pwr</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)]</span>
    <span class="k">for</span> <span class="n">state_idx</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">nbr_states</span><span class="p">)</span>
<span class="p">]</span>
</code></pre></div></div>

<p>This produces <code class="language-plaintext highlighter-rouge">coeffs</code> for \(\varepsilon\) up to the second power:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[[k00, k01, k02], [k10, k11, k12], [k20, k21, k22], [k30, k31, k32], 
 [k40, k41, k42], [k50, k51, k52], [k60, k61, k62], [k70, k71, k72]]
</code></pre></div></div>

<p>Define the stationary distribution as a polynomial in epsilon up to <code class="language-plaintext highlighter-rouge">max_pwr</code></p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># the stationary distribution expressed as a polynomial in epsilon
# v_i = k_i0 eps^0 + k_i1 eps^1 + ...
</span><span class="n">v</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">(</span>
    <span class="p">[</span>
        <span class="nb">sum</span><span class="p">(</span><span class="n">coeffs</span><span class="p">[</span><span class="n">state_idx</span><span class="p">][</span><span class="n">pwr</span><span class="p">]</span> <span class="o">*</span> <span class="n">eps</span><span class="o">**</span><span class="n">pwr</span> <span class="k">for</span> <span class="n">pwr</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">max_pwr</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span>
        <span class="k">for</span> <span class="n">state_idx</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">nbr_states</span><span class="p">)</span>
    <span class="p">]</span>
<span class="p">)</span>
</code></pre></div></div>

<p>This produces <code class="language-plaintext highlighter-rouge">v</code> as a polynomial up to the second power:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>array([eps**2*k02 + eps*k01 + k00, eps**2*k12 + eps*k11 + k10,
       eps**2*k22 + eps*k21 + k20, eps**2*k32 + eps*k31 + k30,
       eps**2*k42 + eps*k41 + k40, eps**2*k52 + eps*k51 + k50,
       eps**2*k62 + eps*k61 + k60, eps**2*k72 + eps*k71 + k70],
      dtype=object)
</code></pre></div></div>

<p>Define the right-hand side of the stationary distribution equation</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># at the stationary distribution, v = vP
</span><span class="n">rhsV</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">v</span> <span class="o">@</span> <span class="n">P</span><span class="p">)</span>
</code></pre></div></div>

<p>This produces <code class="language-plaintext highlighter-rouge">rhsV</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[eps**2*(eps**2*k02 + eps*k01 + k00) + eps**2*(eps**2*k42 + eps*k41 + k40) + eps**2*(eps**2*k52 + eps*k51 + k50) + eps**2*(eps**2*k62 + eps*k61 + k60) + eps*(1 - eps)*(eps**2*k72 + eps*k71 + k70),
 eps**2*(eps**2*k72 + eps*k71 + k70) + eps*(1 - eps)*(eps**2*k02 + eps*k01 + k00) + eps*(1 - eps)*(eps**2*k42 + eps*k41 + k40) + eps*(1 - eps)*(eps**2*k52 + eps*k51 + k50) + eps*(1 - eps)*(eps**2*k62 + eps*k61 + k60),
 eps*(1 - eps)*(eps**2*k02 + eps*k01 + k00) + eps*(1 - eps)*(eps**2*k42 + eps*k41 + k40) + eps*(1 - eps)*(eps**2*k52 + eps*k51 + k50) + eps*(1 - eps)*(eps**2*k62 + eps*k61 + k60) + (1 - eps)**2*(eps**2*k72 + eps*k71 + k70),
 eps*(1 - eps)*(eps**2*k72 + eps*k71 + k70) + (1 - eps)**2*(eps**2*k02 + eps*k01 + k00) + (1 - eps)**2*(eps**2*k42 + eps*k41 + k40) + (1 - eps)**2*(eps**2*k52 + eps*k51 + k50) + (1 - eps)**2*(eps**2*k62 + eps*k61 + k60),
 eps**2*(eps**2*k12 + eps*k11 + k10) + eps**2*(eps**2*k22 + eps*k21 + k20) + eps*(1 - eps)*(eps**2*k32 + eps*k31 + k30),
 eps**2*(eps**2*k32 + eps*k31 + k30) + eps*(1 - eps)*(eps**2*k12 + eps*k11 + k10) + eps*(1 - eps)*(eps**2*k22 + eps*k21 + k20),
 eps*(1 - eps)*(eps**2*k12 + eps*k11 + k10) + eps*(1 - eps)*(eps**2*k22 + eps*k21 + k20) + (1 - eps)**2*(eps**2*k32 + eps*k31 + k30),
 eps*(1 - eps)*(eps**2*k32 + eps*k31 + k30) + (1 - eps)**2*(eps**2*k12 + eps*k11 + k10) + (1 - eps)**2*(eps**2*k22 + eps*k21 + k20)]
</code></pre></div></div>

<p>Now we find equations that we can use to solve for the stationary distribution.
The first equation is the normalisation equation</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># At the stationary distribution, v = rhsV.
# As the error eps -&gt; 0, the stationary distribution approaches
# the epsilon-order 0 coefficients, i.e.,
#   v -&gt; (k_10, k_20, k_30, ...)
</span>
<span class="c1"># solve by matching epsilon-power terms
</span>
<span class="c1"># first, from normalisation of the stationary distribution,
# we always have: 1 = k_10 + k_20 + k_30 + ...
</span><span class="n">pwr_2_eq0s</span> <span class="o">=</span> <span class="p">{</span>
    <span class="mi">0</span><span class="p">:</span> <span class="p">[</span><span class="mi">1</span> <span class="o">-</span> <span class="nb">sum</span><span class="p">(</span><span class="n">coeffs</span><span class="p">[</span><span class="n">state_idx</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">state_idx</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">nbr_states</span><span class="p">))]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>So far, we have <code class="language-plaintext highlighter-rouge">pwr_2_eq0s</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{0: [-k00 - k10 - k20 - k30 - k40 - k50 - k60 - k70 + 1]}
</code></pre></div></div>

<p>The other equations are obtained from \(\boldsymbol{v} = \boldsymbol{v}P\).</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># then, from v = vP, each state has an equation involving various
# epsilon-order terms:
#   k_i0 + k_i1 eps + ... = [term]_i0 + [term]_i1 eps + ...
</span><span class="k">for</span> <span class="n">state_idx</span><span class="p">,</span> <span class="n">rhs</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">rhsV</span><span class="p">):</span>
    <span class="n">pwrs_terms</span> <span class="o">=</span> <span class="n">rhs</span><span class="p">.</span><span class="n">as_poly</span><span class="p">(</span><span class="n">eps</span><span class="p">).</span><span class="n">all_terms</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">pwr_tuple</span><span class="p">,</span> <span class="n">term</span> <span class="ow">in</span> <span class="n">pwrs_terms</span><span class="p">:</span>
        <span class="n">pwr</span> <span class="o">=</span> <span class="n">pwr_tuple</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
        <span class="k">if</span> <span class="n">pwr</span> <span class="o">&lt;=</span> <span class="n">max_pwr</span><span class="p">:</span>
            <span class="n">eq0</span> <span class="o">=</span> <span class="n">term</span> <span class="o">-</span> <span class="n">coeffs</span><span class="p">[</span><span class="n">state_idx</span><span class="p">][</span><span class="n">pwr</span><span class="p">]</span>
            <span class="k">if</span> <span class="n">eq0</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span>  <span class="c1"># exclude not-useful 0 = 0 equations
</span>                <span class="n">pwr_2_eq0s</span><span class="p">.</span><span class="n">setdefault</span><span class="p">(</span><span class="n">pwr</span><span class="p">,</span> <span class="p">[]).</span><span class="n">append</span><span class="p">(</span><span class="n">eq0</span><span class="p">)</span>
</code></pre></div></div>

<p>Now, <code class="language-plaintext highlighter-rouge">pwr_2_eq0s</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{0: [-k00 - k10 - k20 - k30 - k40 - k50 - k60 - k70 + 1,
  -k00,
  -k10,
  -k20 + k70,
  k00 - k30 + k40 + k50 + k60,
  -k40,
  -k50,
  k30 - k60,
  k10 + k20 - k70],
 1: [-k01 + k70,
  k00 - k11 + k40 + k50 + k60,
  k00 - k21 + k40 + k50 + k60 - 2*k70 + k71,
  -2*k00 + k01 - k31 - 2*k40 + k41 - 2*k50 + k51 - 2*k60 + k61 + k70,
  k30 - k41,
  k10 + k20 - k51,
  k10 + k20 - 2*k30 + k31 - k61,
  -2*k10 + k11 - 2*k20 + k21 + k30 - k71],
 2: [k00 - k02 + k40 + k50 + k60 - k70 + k71,
  -k00 + k01 - k12 - k40 + k41 - k50 + k51 - k60 + k61 + k70,
  -k00 + k01 - k22 - k40 + k41 - k50 + k51 - k60 + k61 + k70 - 2*k71 + k72,
  k00 - 2*k01 + k02 - k32 + k40 - 2*k41 + k42 + k50 - 2*k51 + k52 + k60 - 2*k61 + k62 - k70 + k71,
  k10 + k20 - k30 + k31 - k42,
  -k10 + k11 - k20 + k21 + k30 - k52,
  -k10 + k11 - k20 + k21 + k30 - 2*k31 + k32 - k62,
  k10 - 2*k11 + k12 + k20 - 2*k21 + k22 - k30 + k31 - k72],
}
</code></pre></div></div>

<p>We write the list of coefficients we want to solve for</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># we only want to solve the values of k_10, k_20, ...
</span><span class="n">wants</span> <span class="o">=</span> <span class="p">[</span><span class="n">coeffs</span><span class="p">[</span><span class="n">state_idx</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">state_idx</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">nbr_states</span><span class="p">)]</span>
</code></pre></div></div>

<p>This produces <code class="language-plaintext highlighter-rouge">wants</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[k00, k10, k20, k30, k40, k50, k60, k70]
</code></pre></div></div>

<p>We incrementally increase the maximum epsilon power until we have an 
expression for each of the coefficients we want with no free symbols</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># so we match epsilon terms, increasing the epsilon power
# incrementally, until we have taken into account enough rare
# sequences of errors that we can obtain the solutions we want
</span><span class="n">stationary_distn</span> <span class="o">=</span> <span class="nb">list</span><span class="p">()</span>
<span class="n">eq0s</span> <span class="o">=</span> <span class="nb">list</span><span class="p">()</span>
<span class="k">for</span> <span class="n">pwr_level</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">max_pwr</span><span class="o">+</span><span class="mi">1</span><span class="p">):</span>
    
    <span class="c1"># add to our system of equations 
</span>    <span class="c1"># coefficient-matching at current power level
</span>    <span class="n">eq0s</span> <span class="o">+=</span> <span class="n">pwr_2_eq0s</span><span class="p">[</span><span class="n">pwr_level</span><span class="p">]</span>

    <span class="c1"># solve system of equations for epsilon coefficients
</span>    <span class="n">free_coeffs</span> <span class="o">=</span> <span class="nb">set</span><span class="p">.</span><span class="n">union</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">eq0</span><span class="p">.</span><span class="n">free_symbols</span> <span class="k">for</span> <span class="n">eq0</span> <span class="ow">in</span> <span class="n">eq0s</span><span class="p">])</span>
    <span class="n">soln</span> <span class="o">=</span> <span class="n">sp</span><span class="p">.</span><span class="n">solve</span><span class="p">(</span><span class="n">eq0s</span><span class="p">,</span> <span class="n">free_coeffs</span><span class="p">)</span> <span class="c1"># returns [] if can't
</span>
    <span class="c1"># check if solution has all the coefficients we want
</span>    <span class="k">if</span> <span class="nb">all</span><span class="p">(</span><span class="n">want</span> <span class="ow">in</span> <span class="n">soln</span> <span class="k">for</span> <span class="n">want</span> <span class="ow">in</span> <span class="n">wants</span><span class="p">):</span>
        <span class="c1"># got an expression for each coeff we wanted
</span>        <span class="n">stationary_distn_temp</span> <span class="o">=</span> <span class="p">[</span><span class="n">soln</span><span class="p">[</span><span class="n">want</span><span class="p">]</span> <span class="k">for</span> <span class="n">want</span> <span class="ow">in</span> <span class="n">wants</span><span class="p">]</span>
        <span class="k">if</span> <span class="nb">all</span><span class="p">([</span><span class="ow">not</span> <span class="n">propn</span><span class="p">.</span><span class="n">free_symbols</span> <span class="k">for</span> <span class="n">propn</span> <span class="ow">in</span> <span class="n">stationary_distn_temp</span><span class="p">]):</span>
            <span class="c1"># each an expression we got had no unknown variables, so we're done!
</span>            <span class="n">stationary_distn</span> <span class="o">=</span> <span class="n">stationary_distn_temp</span>
            <span class="k">break</span>
</code></pre></div></div>

<p>The loop above breaks at <code class="language-plaintext highlighter-rouge">pwr_level = 1</code>,
giving <code class="language-plaintext highlighter-rouge">soln</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{k00: 0, k01: 3/14, k10: 0, k11: 2/7, k20: 3/14, k21: k71 - 1/7, k30: 2/7, 
 k31: k61 + 5/14, k40: 0, k41: 2/7, k50: 0, k51: 3/14, k60: 2/7, k70: 3/14}
</code></pre></div></div>

<p>And so the stationary distribution as \(\varepsilon \rightarrow 0\) is <code class="language-plaintext highlighter-rouge">stationary_distn</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[0, 0, 3/14, 2/7, 0, 0, 2/7, 3/14]
</code></pre></div></div>

<h3 id="future-work">Future work</h3>

<p>One thing I’m unclear about is how to determine the maximum power <code class="language-plaintext highlighter-rouge">max_pwr</code> that is needed to find the solution.
My intuition tells me that the maximum power is probably the maximum number of errors needed to escape from one attractor to another, such that all attractors are eventually reachable.
One could check only the states in each attractor
for their shortest route to other basins.
However, the shortest route of escape might pass through a transient state in the attractor’s own basin.
Though even if that’s the case, it might not matter for much practical purposes;
there’s no great harm in overestimating <code class="language-plaintext highlighter-rouge">max_pwr</code>, as I do in the example code above,
it just means more computations.</p>

<p>I’m sure that someone has used this method before,
but I haven’t been able to find it in the literature.
I found a book Kato (1980),
which has Chapter 2 dedicated to perturbation theory;
however, the form of perturbation it considers is different from the one I use here.
I also found Kandori et al. (1993),
which has a very similar-looking polynomial;
however, 
their method seems to require finding all possible structures they call “z-trees”,
which is computationally expensive.</p>

<h3 id="references">References</h3>

<p>Kandori, M., Mailath, G. J. and Rob, R. (1993). Learning, mutation, and long run equilibria in games. Econometrica: Journal of the Econometric Society, 61(1):29-56.</p>

<p>Kato, T. (1980) Perturbation Theory for Linear Operators. Springer-Verlag, Berlin.</p>

<p>Kleshnina, M., Hilbe, C., Šimsa, Š., Chatterjee, K. and Nowak, M.A. (2023). The effect of environmental information on evolution of cooperation in stochastic games. Nature Communications, 14(1):4153.</p>]]></content><author><name>nadiah_kristensen</name></author><category term="cooperation" /><summary type="html"><![CDATA[For the past year, I’ve been working in my spare time through a very interesting paper by Kleshnina et al. (2023). The paper concerns an evolutionary game theory model where individuals play an iterated Prisoner’s Dilemma with an environmental feedback. In a previous post, I experimented with a method to automate the identification of the subgame-perfect Nash equilibria and their parameter-value conditions.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://nadiah.org/wp-content/uploads/2024/11/cycle_example.png" /><media:content medium="image" url="https://nadiah.org/wp-content/uploads/2024/11/cycle_example.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Evolution of cooperation talk at Griffith University</title><link href="https://nadiah.org/2025/05/09/griffith-cooperation" rel="alternate" type="text/html" title="Evolution of cooperation talk at Griffith University" /><published>2025-09-24T04:44:54+00:00</published><updated>2025-09-24T04:44:54+00:00</updated><id>https://nadiah.org/2025/05/09/griffith_cooperation</id><content type="html" xml:base="https://nadiah.org/2025/05/09/griffith-cooperation"><![CDATA[<p>Today,
I gave a talk at <a href="https://www.griffith.edu.au/">Griffith University’s</a> 
about my two most recent papers about the evolution of cooperation,
published in <a href="https://www.nature.com/articles/s41598-022-24590-y">Scientific Reports</a> 
and <a href="https://www.sciencedirect.com/science/article/pii/S0022519325000554">Journal of Theoretical Biology</a>.
You can download the full slide deck here: <a href="/wp-content/uploads/2025/09/evoln_coopn.pdf">evoln_coopn.pdf</a>.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2025/09/griffith_4_pages.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2025/09/griffith_4_pages.png" alt="Some slides from my talk" />
    </a>
    
    <figcaption><span><p>Some slides from my talk</p>
</span></figcaption>
</figure>

<p>I have taken an adjunct position there, in addition to my position at 
<a href="https://www.qut.edu.au/">QUT</a>,
and I’m looking forward to regular visits to the lovely Nathan Campus,
which is nestled in <a href="https://tooheyforesteec.eq.edu.au/">Toohey Forest</a>.</p>]]></content><author><name>nadiah_kristensen</name></author><category term="cooperation" /><summary type="html"><![CDATA[Today, I gave a talk at Griffith University’s about my two most recent papers about the evolution of cooperation, published in Scientific Reports and Journal of Theoretical Biology. You can download the full slide deck here: evoln_coopn.pdf.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://nadiah.org/wp-content/uploads/2025/09/griffith_p41.png" /><media:content medium="image" url="https://nadiah.org/wp-content/uploads/2025/09/griffith_p41.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Cooperation among unequals</title><link href="https://nadiah.org/2025/06/01/cooperation-unequals" rel="alternate" type="text/html" title="Cooperation among unequals" /><published>2025-06-01T04:44:54+00:00</published><updated>2025-06-01T04:44:54+00:00</updated><id>https://nadiah.org/2025/06/01/cooperation-unequals</id><content type="html" xml:base="https://nadiah.org/2025/06/01/cooperation-unequals"><![CDATA[<p>In an unpublished paper, 
<a href="http://manuelstaab.com/research/optimal sharing soc dilemma.pdf">Staab et al. (2022)</a>
studied the effects of heterogeneity in endowments and
productivities on cooperation. They were particularly interested in the effect of allowing players to
share the jointly produced goods unequally. Surprisingly, they found that unequal sharing can promote
cooperation.</p>

<p>In this blog post,
first, I offer a proof of one of their propositions.
Then I try thinking about their model a little differently to how they presented it,
working through some small examples to try to get a visual intuition for what it means.</p>

<h3 id="signs-of-eigenvalues-proof">Signs of eigenvalues proof</h3>

<p>Staab et al. (2022) model an \(n\)-player 
iterated public goods game with a continuation probability \(\delta\).
Each round, individual \(i\) receives endowment \(e_i\) that they can either contribute
to the public good or keep.
Their strategy is deterministic and fixed for all rounds.
If individual \(i\) contributes to the public good,
their contribution is multiplied by their productivity \(r_i\).
The return from the public good is shared among the members of the group
according to a <em>sharing rule</em> \(\boldsymbol{f} = (f_1, \ldots, f_n)\),
where \(f_i\) is the proportion of the jointly produced good that individual \(i\) receives.</p>

<p>The optimal sharing rule and minimum continuation probability pair 
\((\boldsymbol{f}^*, \delta_{\text{min}})\)
solves an eigenvalue problem</p>

\[\begin{equation*}
    \delta_{\text{min}} \boldsymbol{f}^* = \Phi \boldsymbol{f}^*,
\end{equation*}\]

<p>where the matrix \(\Phi\) has elements</p>

\[\begin{equation}
    \phi_{ij} = 
        \begin{cases}
            \frac{-e_i (r_i - 1)}{\sum_{k \neq i} e_k r_k}, &amp; i = j, \\
            \frac{e_i}{\sum_{k \neq i} e_k r_k}, &amp; i \neq j.
        \end{cases}
    \label{phi_ij}
    \tag{1}
\end{equation}\]

<p>In Proposition 4, they state:</p>
<blockquote>
  <p>“the optimal sharing rule [\(f^*\)] is the eigenvector that corresponds to the
largest eigenvalue of the matrix \(\Phi\)”</p>
</blockquote>

<p>and</p>

<blockquote>
  <p>“the largest eigenvalue is the minimal continuation probability
(\(\delta_{\text{min}})\) for this sharing rule to be feasible”</p>
</blockquote>

<p>Below, I offer a proof that \(\delta_{\text{min}}\) is indeed the largest eigenvalue
— the only eigenvalue that has the potential to be positive —
and that it is positive only when their Assumption 1 is satisfied
(non-triviality of cooperation):</p>

\[\begin{equation}
    \sum_i \frac{1}{r_i} &gt; 1.
    \label{reciprocal_productivity_condition}
    \tag{2}
\end{equation}\]

<p>The theorem can be proved by analysing the characteristic polynomial of \(\Phi\)
using a similar approach to Meyer (2000, Ex. 7.1.22).</p>

<div class="content-highlight">

<p>Let \(\Phi\) be the matrix defined by Eq. \ref{phi_ij}. Then:</p>
<ol>
  <li>At least \(n-1\) eigenvalues of \(\Phi\) are negative.</li>
  <li>The largest eigenvalue \(\lambda_1\) of \(\Phi\) is positive if and only if
 the reciprocal productivity condition from Assumption 1 
 (Eq. \ref{reciprocal_productivity_condition}) is satisfied.</li>
</ol>

</div>

<p><em>Proof</em>.
Matrix \(\Phi\) can be rewritten as a rank-one update of a diagonal matrix
(e.g., Meyer, 2000, Sect. 6.2)</p>

\[\begin{equation*}
    \Phi = D + \boldsymbol{b} \boldsymbol{1}^T,
\end{equation*}\]

<p>where \(D\) is a diagonal matrix with elements</p>

\[\begin{equation*}
    d_i = \frac{- e_i r_i}{\sum_{k \neq i} e_k r_k} &lt; 0,
\end{equation*}\]

<p>and \(\boldsymbol{b}\) is a column vector with elements</p>

\[\begin{equation*}
    b_i = \frac{e_i}{\sum_{k \neq i} e_k r_k} &gt; 0.
\end{equation*}\]

<p>Consider the corresponding eigenvalue problem</p>

\[\begin{equation*} 
\lambda \boldsymbol{v} = (D + \boldsymbol{b} \boldsymbol{1}^T) \boldsymbol{v}
= D \boldsymbol{v} + \boldsymbol{b} \boldsymbol{1}^T \boldsymbol{v} = D \boldsymbol{v} + c \boldsymbol{b},
\end{equation*}\]

<p>where \(\boldsymbol{1}^T \boldsymbol{v} = c\) is a constant. 
Rearrange to obtain
\(1 = \boldsymbol{1}^T (\lambda I - D)^{-1} \boldsymbol{b}\).
Since \(\lambda I - D\) is a diagonal matrix,
\((\lambda I - D)^{-1}\) is also a diagonal matrix 
but with reciprocal elements to the original.
Therefore, the eigenvalues solve \(1 = \sum_{i=1}^n \frac{b_i}{\lambda - d_i}\),
which can be rearranged to obtain the characteristic polynomial
of \(\Phi\):</p>

\[\begin{equation*} 
    f(\lambda) = \prod_{i=1}^n (\lambda - d_i) - \sum_{i=1}^n b_i \prod_{j\neq i} (\lambda - d_j).
\end{equation*}\]

<p>To partition the repeated and unique eigenvalues, we factorise the characteristic polynomial \(f(\lambda)\).
First, we index the unique diagonal elements of \(D\) in descending order
\(d_{(1)} &gt; d_{(2)} &gt; ... &gt; d_{(m)}\), where \(m \leq n\),
and define the corresponding index sets:</p>

\[\begin{equation*} 
    I_{(i)} \equiv \left\{j \in \{1, 2, \ldots, n\} \mid d_j = d_{(i)}\right\}.
\end{equation*}\]

<p>Second, we define the sum of \(b_j\) elements in the same index set:</p>

\[\begin{equation*}
    B_{(i)} \equiv \sum_{j \in I_{(i)}} b_j.
\end{equation*}\]

<p>Then we can factorise the characterisic polynomial as \(f(\lambda) = g(\lambda) h(\lambda)\),
where</p>

\[\begin{equation*}
    g(\lambda) = \prod_{l=1}^m (\lambda - d_{(l)})^{|I_{(l)}|-1} ,
\end{equation*}\]

<p>and</p>

\[\begin{equation}
    h(\lambda) =
        \prod_{k=1}^m (\lambda - d_{(k)}) 
        - \sum_{i=1}^m B_{(i)} \prod_{j \neq i} (\lambda - d_{(j)}).
    \label{h(lambda)}
    \tag{3}
\end{equation}\]

<div class="content-highlight-grey">

<p><strong>Example 1.</strong> Consider a game played with 
\(\boldsymbol{e} = (0.2, 0.2, 0.2, 0.4)\) and
\(\boldsymbol{r} = (2, 2, 2, 3)\).
Then</p>

\[\begin{align*}
            \Phi &amp; = 
            \begin{pmatrix}
                -0.10 &amp; 0.10 &amp; 0.10 &amp; 0.10 \\
                0.10 &amp; -0.10 &amp; 0.10 &amp; 0.10 \\
                0.10 &amp; 0.10 &amp; -0.10 &amp; 0.10 \\
                0.33 &amp; 0.33 &amp; 0.33 &amp; -0.67 \\
            \end{pmatrix} \\
            &amp;=
            \underbrace{
            \begin{pmatrix}
                -0.2 &amp; 0 &amp; 0 &amp; 0 \\
                0 &amp; -0.2 &amp; 0 &amp; 0 \\ 
                0 &amp; 0 &amp; -0.2 &amp; 0 \\ 
                0 &amp; 0 &amp; 0 &amp; -1.0 \\ 
            \end{pmatrix}
            }_{D}
            + 
            \underbrace{
            \begin{pmatrix}
                0.10 &amp; 0.10 &amp; 0.10 &amp; 0.10 \\
                0.10 &amp; 0.10 &amp; 0.10 &amp; 0.10 \\
                0.10 &amp; 0.10 &amp; 0.10 &amp; 0.10 \\
                0.33 &amp; 0.33 &amp; 0.33 &amp; 0.33 \\
            \end{pmatrix}
            }_{\boldsymbol{b} \boldsymbol{1}^T}
        \end{align*}\]

<p>The characteristic polynomial</p>

\[\begin{equation*}
        f(\lambda) = (\lambda + 0.2)^3 (\lambda+1)
        - 0.3 (\lambda + 0.2)^2 (\lambda + 1)
        - 0.33(\lambda + 0.2)^3.
    \end{equation*}\]

<p>Indexing the unique diagonal elements:</p>

\[\begin{alignat*}{6}
        &amp;d_{(1)} &amp;&amp;= -0.2, \qquad &amp;&amp;I_{(1)} &amp;&amp;= \{ 1, 2, 3 \}, \qquad &amp;&amp;B_{(1)} &amp;&amp;= 0.3, \\
        &amp;d_{(2)} &amp;&amp;= -1,   \qquad &amp;&amp;I_{(2)} &amp;&amp;= \{ 4 \},       \qquad &amp;&amp;B_{(2)} &amp;&amp;= 0.33. 
    \end{alignat*}\]

<p>So the characteristic polynomial can be factorised as</p>

\[\begin{equation*}
        f(\lambda) 
        = \underbrace{ \left[ (\lambda + 0.2)^2 \right] }_{g(\lambda)}
        \underbrace{ \left[ 
            (\lambda + 0.2) (\lambda + 1) - 0.3 (\lambda + 1) - 0.33 (\lambda + 0.2)
        \right] }_{h(\lambda)}.
    \end{equation*}\]

</div>

<p>The roots of \(g(\lambda)\) correspond to the repeated diagonal elements of \(D\),
which are all negative. 
Specifically, 
for each unique diagonal element \(d_{(\ell)}\) with multiplicity \(|I_{(\ell)}| &gt; 1\),
\(g(\lambda)\) contributes \(|I_{(\ell)}| - 1\) roots to 
the characteristic polynomial \(f(\lambda)\),
and these roots are equal to \(d_{(\ell)}\).
The roots of \(g(\lambda)\) 
correspond to the eigenvalues of \(\Phi\) that are unchanged by the rank-one update.
Since \(d_{(\ell)} &lt; 0\) for all \(\ell\) (due to the definition of \(D\)), 
the roots of \(g(\lambda)\) correspond to negative eigenvalues of \(\Phi\). 
The total number of such eigenvalues is \(\sum_{\ell=1}^m (|I_{(\ell)}| - 1) = n - m\).
Therefore, \(g(\lambda)\) accounts for \(n - m\) negative eigenvalues of \(\Phi\).</p>

<p>The remaining \(m\) eigenvalues are determined by the roots of \(h(\lambda)\). 
To characterise them,
we will show that:</p>
<ol>
  <li>\(h(d_{(\ell)})\) alternates in sign with \(\ell\) according to: 
\(\text{sign}(h(d_{(\ell)})) = (-1)^\ell\) for \(\ell = 1, \ldots, m\).</li>
  <li>\(h(L) &gt; 0\) for sufficiently large \(L\).</li>
  <li>\(h(0) &lt; 0\) if and only if \(\sum_{i=1}^n \frac{1}{r_i} &gt; 1\) (the reciprocal productivity condition).</li>
</ol>

<p>To show Point 1,
we analyse the signs of the terms in Eq. \ref{h(lambda)}.
First, we can simplify \(h(d_{(\ell)})\)</p>

\[\begin{align*}
    h(d_{(\ell)}) 
    &amp;= \underbrace{\prod_{k=1}^m (d_{(\ell)} - d_{(k)})}_{0}
        - \sum_{i=1}^m B_{(i)} 
        \underbrace{\prod_{j \neq \ell} (d_{(\ell)} - d_{(j)})}_{0 \text{ except when } i = \ell}, \\
    &amp;= -B_{(\ell)} \prod_{j \neq \ell} (d_{(\ell)} - d_{(j)}).
\end{align*}\]

<p>From the ordering \(d_{(1)} &gt; d_{(2)} &gt; ... &gt; d_{(m)}\),
the signs of the terms in the product are known:</p>

\[\begin{equation*}
    \text{sign}(d_{(\ell)} - d_{(j)}) =
    \begin{cases}
    +1 &amp; \text{ if } \ell &lt; j, \\
    -1 &amp; \text{ if } \ell &gt; j.
    \end{cases}
\end{equation*}\]

<p>Therefore,
\(h(d_{(\ell)})\) is the product of: 
the negative term \(-B_{(\ell)}\),
\(\ell-1\) negative terms, 
and \(m - \ell\) positive terms.
Consequently,
\(\text{sign}(h(d_{(\ell)})) = (-1)^{\ell}\).</p>

<div class="content-highlight-grey">

<p><strong>Example 2.</strong> Show that \(h(d_{(1)}) &lt; 0\) and \(h(d_{(2)}) &gt; 0\).</p>

<p>For \(h(d_{(1)})\):</p>

\[\begin{align*}
    h(d_{(1)}) 
    &amp;= \underbrace{\prod_{k=1}^m (d_{(1)} - d_{(k)})}_{0} - \sum_{i=1}^m B_i 
        \underbrace{\prod_{j \neq i} (d_{(1)} - d_{(j)})}_{0 \text{ except when } i = 1}, \\
    &amp;= - B_{(1)} \prod_{j \neq 1} \underbrace{(d_{(1)} - d_{(j)})}_{d_{(1)} &gt; d_{(j)} \forall j \neq 1} &lt; 0.
\end{align*}\]

<p>For \(h(d_{(2)})\):</p>

\[\begin{align*}
    h(d_{(2)}) 
    &amp;= \underbrace{\prod_{k=1}^m (d_{(2)} - d_{(k)})}_{0} - \sum_{i=1}^m B_i 
        \underbrace{\prod_{j \neq i} (d_{(2)} - d_{(j)})}_{0 \text{ except when } i = 2}, \\
    &amp;= - B_{(2)} \prod_{j \neq 2} (d_{(2)} - d_{(j)}), \\
    &amp;= - B_{(2)} \underbrace{(d_{(2)} - d_{(1)})}_{(-)} 
        \underbrace{(d_{(2)} - d_{(3)})}_{(+)} 
        \underbrace{(d_{(2)} - d_{(4)})}_{(+)} 
        \ldots \underbrace{(d_{(2)} - d_{(m)})}_{(+)} \\
    &amp;&gt; 0.
\end{align*}\]

</div>

<p>To show Point 2,
we show that there exists some large-enough \(L\) such that \(h(L) &gt; 0\).
Rearranging Eq. \ref{h(lambda)}:</p>

\[\begin{equation*}
    h(L) = 
        \underbrace{\left( \prod_{k=1}^m \left( L - d_{(k)} \right) \right)}_{\text{LHT}}
        \underbrace{\left( 1 - \sum_{i=1}^m \frac{B_{(i)}}{L - d_{(i)}} \right)}_{\text{RHT}}.
\end{equation*}\]

<p>If \(L &gt; d_{(1)}\), 
then the left-hand term \(\text{LHT} &gt; 0\).
If \(\sum_{i=1}^m \frac{B_{(i)}}{L - d_{(i)}} &lt; 1\),
then the right-hand term \(\text{RHT} &gt; 0\).
We observe:</p>

\[\begin{equation*}
    \sum_{i=1}^m \frac{B_{(i)}}{L - d_{(i)}}
    &lt; \sum_{i=1}^m \frac{\text{max}(\{B_{(i)}\})}{L - d_{(i)}} 
    &lt; m \frac{\text{max}(\{B_{(i)}\})}{L - d_{(1)}}.
\end{equation*}\]

<p>Therefore, if \(L &gt; m \cdot \text{max}(\{B_{(i)}\}) + d_{(1)}\), 
then \(h(L) &gt; 0\).</p>

<p>To show Point 3,
we substitute \(\lambda = 0\) into Eq. \ref{h(lambda)}</p>

\[\begin{equation*}
    h(0) = \left(\prod_{k=1}^m (- d_{(k)}) \right) \left( 1 - \sum_{i=1}^n \frac{1}{r_i} \right).
\end{equation*}\]

<p>The left-hand term is positive.
Therefore, \(h(0) &lt; 0\) if and only if \(\sum_{i=1}^n \frac{1}{r_i} &gt; 1\).</p>

<p>Using Points 1, 2, and 3 to analyse the sign of \(h(\lambda)\),
we can locate the roots and identify the signs of the eigenvalues of \(\Phi\).</p>

<div class="content-highlight-grey">

<p><strong>Example 3.</strong> 
Consider a 5-player game with</p>

\[\boldsymbol{e} = (0.3, 0.2, 0.2, 0.2, 0.1)\]

<p>and</p>

\[\boldsymbol{r} = (4, 3, 3, 3, 2)\]

<p>which gives</p>

\[\boldsymbol{d} = (-0.60, -0.23, -0.23, -0.23, -0.07)\]

<p>The figure below shows
the relationship between the characteristic polynomial \(f(\lambda)\), 
its factor \(h(\lambda)\), the unique diagonal elements of \(D\) \(d_{(i)}\), 
and the eigenvalues of \(\Phi\).
The repeated eigenvalues correspond to repeated \(d_i\).
Function \(h(\lambda)\) alternates in sign at each unique \(d_i\).
The sign of the largest eigenvalue \(\lambda_1\) is determined by the sign of \(h(0)\).</p>

<p><img src="/wp-content/uploads/2025/06/eigenvalue_signs.png" alt="" /></p>

</div>

<p>From Point 1,
we know that \(h(\lambda)\) has \(m\) roots strictly interlacing 
the \(d_{(\ell)}\) values.
The \(d_{(\ell)}\) values are negative.
Therefore,
\(m-1\) roots satisfy \(d_{(\ell + 1)} &lt; \lambda &lt; d_{(\ell)}\) and are negative.</p>

<p>The \(m-1\) negative roots of \(h(\lambda)\) combine with the \(n-m\) negative roots of \(g(\lambda)\)
to account for \(n-1\) eigenvalues of \(\Phi\).
Therefore,
we can conclude that at least \(n-1\) eigenvalues of \(\Phi\) are negative.</p>

<p>The remaining root of \(h(\lambda)\) might satisfy either \(\lambda &lt; d_{(m)}\) or \(\lambda &gt; d_{(1)}\).
From Point 2,
since \(h(d_{(1)}) &lt; 0\) and \(h(L) &gt; 0\) for some large \(L\),
then the remaining root must be the largest root of \(\Phi\)
and satisfy \(d_{(1)} &lt; \lambda_1 &lt; L\).
Therefore, in summary so far, we have</p>

\[\begin{equation*}
    \overbrace{d_{(m)} &lt; \lambda_m &lt; d_{(m-1)} &lt; \ldots &lt; d_{(2)} &lt; \lambda_2 &lt;
    \underbrace{d_{(1)}}_{(-)}}^{\text{$m-1$ negative roots of $h(\lambda)$ interlacing $d_{(1)}$ to $d_{(m)}$}}
     &lt; \lambda_1 &lt; \underbrace{L}_{(+)}.
\end{equation*}\]

<p>The sign of \(\lambda_1\) is determined by the sign of \(h(0)\).
In particular, \(\lambda_1 &gt; 0\) if and only if \(h(0) &lt; 0\).
From Point 3,
\(h(0) &lt; 0\) if and only if \(\sum_{i=1}^n 1/r_i &gt; 1\),
where the latter 
is the reciprocal productivity condition from Assumption 1
(Eq. \ref{reciprocal_productivity_condition}).
Therefore, 
we conclude that \(\lambda_1 &gt; 0\) if and only if 
Assumption 1 is satisfied.</p>

<h3 id="try-to-get-a-visual-intuition">Try to get a visual intuition</h3>

<p>The folk theorem tells us that, if players in an iterated game are sufficiently patient
(i.e., the discount factor \(\delta \rightarrow 1\)),
then any feasible and individually rational average payoff can be sustained in a subgame-perfect equilibrium
via a generalised Grim-trigger strategy.
In particular,
taking into account that the Nash equilibrium of the stage game NE is a subgame-perfect equilibrium,
then a player will be willing to stick to any alternative strategy to NE, call it ALT,
so long as the payoff from ALT is greater than or equal to the maximum payoff 
they could obtain from a one-shot deviation (MAX DEVN)
plus reversion to NE for the remainder of the game</p>

\[\begin{equation}
    \pi_k(\text{ALT}) \geq (1 - \delta) \; \pi_k(\text{MAX DEVN}) + \delta \; \pi_k(\text{NE}).
\end{equation}\]

<p>The following equation is given (between Eqs. 1 and 2 in Staab et al.)
for the Grim strategy</p>

\[\begin{equation}
    \underbrace{f_k \sum_i e_i \; r_i}_{\text{payoff from full coop}}
    \geq \underbrace{(1 - \delta) \; f_k \; \sum_{j \neq k} e_j \; r_j + e_k}_{\text{payoff from one-shot devn + reversion}}.
    \label{eq:staab_1.5}
    \tag{4}
\end{equation}\]

<p>We could generalise this a bit to obtain the space of feasible rational payoffs of all alternative strategies.</p>

<p>First, 
the frontier of the feasible set is defined by the payoffs from full cooperation
with \(\sum_i f_i = 1\).
Second, 
the maximum possible payoff that focal player \(k\) can obtain from a one-shot deviation occurs when:
player \(k\)’s deviation is to defect instead of cooperate,
and the deviation is timed to occur when all non-focal players choose cooperate.
Therefore,
the right-hand side of Eq. \ref{eq:staab_1.5} defines the bounds of the individually rational set.
Therefore,
the set of feasible and rational alternative-strategy payoffs are the values between the left-
and right-hand sides of Eq. \ref{eq:staab_1.5}</p>

\[\begin{equation}
    \underbrace{f_k \sum_i e_i \; r_i}_{\text{payoff from full coop}}
    \geq \pi_k(\text{ALT}) 
    \geq \underbrace{(1 - \delta) \; f_k \; \sum_{j \neq k} e_j \; r_j + e_k}_{\text{payoff from one-shot devn + reversion}}.
    \label{eq:my_staab_1.5}
    \tag{5}
\end{equation}\]

<p>When \(\delta = 1\),
Eq. \ref{eq:my_staab_1.5} reduces to</p>

\[\begin{equation*}
    \underbrace{f_k \sum_i e_i \; r_i}_{\text{payoff from full coop}}
    \geq \pi_k(\text{ALT}) 
    \geq e_k,
\end{equation*}\]

<p>where \(\boldsymbol{e}\) has the natural interpretation as the disagreement point.</p>

<p>As \(\delta\) increases,
the space between the left- and right-hand sides Eq. \ref{eq:my_staab_1.5} contracts
until we find the point where</p>

\[\begin{equation*}
    \underbrace{f_k^* \sum_i e_i \; r_i}_{\text{payoff from full coop}}
    = \pi_k(\text{ALT}) 
    = \underbrace{(1 - \delta^*) \; f_k^* \; \sum_{j \neq k} e_j \; r_j + e_k}_{\text{payoff from one-shot devn + reversion}}.
\end{equation*}\]

<p>It is finding this point that is the main focus of Staab et al.;
in particular, finding the value of the pair \((\boldsymbol{f}^*, \delta^*)\).</p>

<h4 id="a-visual-example">A visual example</h4>

<p>Consider a 2-player scenario where</p>

\[\begin{equation*}
    \boldsymbol{e} = (0.2, 0.8), \text{ and } \boldsymbol{r} = (2, 1.5).
\end{equation*}\]

<p>At full cooperation,
the maximum possible quantity of goods is produced</p>

\[\begin{equation*}
    g = e_1 \; r_1 + e_2 \; r_2
\end{equation*}\]

<p>When those goods are efficiently shared,
i.e., \(f_1 + f_2 = 1\),
each player receives</p>

\[\begin{align*}
    \pi_1 &amp;= f_1 \; g, \\
    \pi_2 &amp;= (1 - f_1) \; g.
\end{align*}\]

<p>This describes the function that
defines the upper limit of the feasible set,
i.e., the frontier, which is shown as a blue line in Fig. 1.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2025/06/example_1.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2025/06/example_1.png" alt="Figure 1. A two-player example." />
    </a>
    
    <figcaption><span><p>Figure 1. A two-player example.</p>
</span></figcaption>
</figure>

<p>If the game is repeated forever,
i.e., the continuation probability \(\delta = 1\),
then the maximum deviation payoff players can receive is 
equal to the payoff from private investment</p>

\[\begin{equation*}
    \text{max. deviation payoff}_k = e_k.
\end{equation*}\]

<p>For each player \(k\),
this is the minimum game payoff that a rational player should be willing to receive from the game,
which are shown by red lines in Fig. 1.</p>

<p>Combining the upper limit defining the feasible set
and the lower limits defining the minimum individually rational payoffs for all players
defines the feasible and rational payoffs region 
(shaded red in Fig. 1).
Any combination of strategies and sharing agreements (\(\boldsymbol{f}\))
that provide payoffs within this region can be sustained.</p>

<p>If the continuation probability \(\delta &lt; 1\),
then the maximum deviation payoff (normalised) includes
the payoff from the deviation-round itself</p>

\[\begin{equation*}
    \text{max. deviation payoff}_k = e_k + (1-\delta) \; f_k \; \sum_{j \neq k} e_j r_j.
\end{equation*}\]

<p>Therefore, decreasing \(\delta\) shrinks the feasible rational region
(e.g., when \(\delta = 0.5\), the region shrinks to the blue shaded region in Fig. 1),
which reduces the number of strategies and sharing agreements 
that can be sustained.</p>

<p>Staab et al. 
are interested in finding the \(\delta_{\text{min}}\) 
for which the feasible-rational region shrinks
to a single point.
In Fig. 1,
this point occurs when \(\delta = \delta_{\text{min}} = 0.13\),
which corresponds to payoffs \((\pi_1^*, \pi_2^*) \approx (0.58, 1.02)\) on the frontier,
with sharing agreement \((f_1^*, f_2^*) \approx (0.36, 0.64)\).</p>

<h3 id="some-more-examples">Some more examples</h3>

<h4 id="productivities-equal-vary-outside-options">Productivities equal, vary outside options</h4>

<p>Consider an example with equal productivities</p>

\[\begin{equation*}
    \boldsymbol{r} = (1.5, 1.5)
\end{equation*}\]

<p>where we vary the outside options (Fig. 2).
The poorer player receives the smaller share,
e.g., for the purple line, Player 1 is the poorer player.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2025/06/example_2.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2025/06/example_2.png" alt="Figure 2. Two-player examples with equal productivities and different outside options. Note that the distance along the critical-point line is not reliably indicative of $$\delta_{\text{min}}$$.
            For the unequal-endowments scenarios, $$\delta_{\text{min}} \approx 0.11$$, whereas for the equal-endowments scenario, $$\delta_{\text{min}} \approx 0.33$$." />
    </a>
    
    <figcaption><span><p>Figure 2. Two-player examples with equal productivities and different outside options. Note that the distance along the critical-point line is not reliably indicative of \(\delta_{\text{min}}\).
            For the unequal-endowments scenarios, \(\delta_{\text{min}} \approx 0.11\), whereas for the equal-endowments scenario, \(\delta_{\text{min}} \approx 0.33\).</p>
</span></figcaption>
</figure>

<p>We can predict the outcomes of higher and lower productivity levels
— while keeping \(r_1 = r_2\) — by moving the frontier (blue line)
up and down.
When productivities are low,
\(\delta_{\text{min}} \rightarrow 1\),
and the shares reflect the outside option.
But as productivity increases,
\(\delta_{\text{min}}\) decreases,
and the shares become more equal.
The shares meet at equal shares,
which occurs when \(r\) is high enough that it no longer presents a social dilemma.
At this point, \(\delta_{\text{min}} = 0\),
which makes this a one-shot game.</p>

<p>What does it mean that players demand an equal share in the one-shot game,
regardless of their outside option?
When \(\boldsymbol{r} = (2, 2)\),
the payoffs they receive from the game are greater than their outside options.
But recall that the fact a cooperative solution gives both players higher payoffs 
is not enough to guarantee it.
From the eigenvalues proof,
we need the leading eigenvalue \(\lambda_1 = 0\),
which implies \(h(0) = 0\),
which implies \(\sum_i 1/r_i = 1\).
For equal \(r_i\),
that implies \(r_i = 2\).
The social dilemma condition is that the return on investment is less than one</p>

\[\begin{equation*}
    f_k \; r_k &lt; 1 \text{ for all } k \in \mathcal{N}
\end{equation*}\]

<p>and, when \(r_k = 2\), this condition is only satisfied by \(f_k = 0.5\).</p>

<p>Or to think about it another way,
when \(r_i = 2\),
the situation is on the cusp of transition from PD to mutualism.
The payoff matrix is</p>

<table class="narrow-table">
  <thead>
    <tr>
      <th> </th>
      <th>C</th>
      <th>D</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>C</td>
      <td>1, 1</td>
      <td>0.1, 1</td>
    </tr>
    <tr>
      <td>D</td>
      <td>1, 0.9</td>
      <td>0.1, 0.9</td>
    </tr>
  </tbody>
</table>

<p>where the calculation for the cooperative payoff is</p>

\[\begin{equation*}
    \pi_1(C, C) = f_1 (e_1 \; r_1 + e_2 \; r_2) = 0.5 ( 0.9 \times 2 + 0.1 \times 2) = 1,
\end{equation*}\]

<p>and the deviations (off-diagonal elements of the payoff matrix) are</p>

\[\begin{equation*}
    \pi_1(D, C) = f_1 \; e_2 \; r_2 + e_1 = 0.5 \times 0.9 \times 2 + 0.1 = 1,
\end{equation*}\]

<p>and</p>

\[\begin{equation*}
    \pi_2(C, D) = f_2 \; e_1 \; r_1 + e_2, = 0.5 \times 0.1 \times 2 + 0.9 = 1.
\end{equation*}\]

<h4 id="productivities-unequal">Productivities unequal</h4>

<p>In this final subsection,
I tried playing around with an example with unequal productivities,
approaching it from different angles to see if it could help my intuition.</p>

<p>Consider an example with unequal productivities</p>

\[\begin{equation*}
    \boldsymbol{r} = (1.1, 2.2)
\end{equation*}\]

<p>and use Fig. 3 as our guide.
I’m curious about the point where the disagreement-point trajectory
hits the diagonal line, the special points marked with stars in Fig. example_3
and the grey line they align upon.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2025/06/example_3.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2025/06/example_3.png" alt="Figure 3. Two-player examples with unequal productivities.
Player 2 is the more productive player." />
    </a>
    
    <figcaption><span><p>Figure 3. Two-player examples with unequal productivities.
Player 2 is the more productive player.</p>
</span></figcaption>
</figure>

<p>The dashed lines are equivalent
to other full-cooperation payoffs when the \(r_i\) are increased but kept at the same ratio.
For this example</p>

\[\begin{equation}
    \frac{r_1}{r_2} = \frac{1}{2}.
    \label{const_ratio}
    \tag{6}
\end{equation}\]

<p>From my eigenvalue equation,
I know that \(\delta_{\text{min}} = 0\) when \(h(0) = 0\),
which occurs when</p>

\[\begin{equation}
    \frac{1}{r_1^{\star}} + \frac{1}{r_2^{\star}} = 1.
    \label{lift_line}
    \tag{7}
\end{equation}\]

<p>This describes the dashed line where the star-points occur.
Solving Eq. \ref{const_ratio} and Eq. \ref{lift_line} simultaneously,
I obtain</p>

\[\begin{equation}
    r_1^{\star} = 1.5 \text{ and } r_2^{\star} = 3
\end{equation}\]

<p>I also know that, at the cusp of the transition from PD to mutualism,
\(f_k \; r_k = 1\).
Therefore,</p>

\[\begin{equation}
    f_k^{\star} = \frac{1}{r_k^{\star}}.
\end{equation}\]

<p>I took the long route to get here.
One can also get this equation by subbing \(\delta = 0\) into the \(f_k\) equation with any
\(r\) values provided we use the same goods equation because we’re keeping the \(r\) ratios constant.</p>

<p>Anyway, in this example</p>

\[\begin{equation}
    (f_1^{\star}, f_2^{\star}) = (2/3, 1/3).
\end{equation}\]

<p>For any given \(\boldsymbol{e}\),
the equation for the maximum public good produced is</p>

\[\begin{equation}
    g^{\star}(\boldsymbol{e}) = \sum_i e_i \; r_i^{\star}
\end{equation}\]

<p>So I can find the star-points on the graph with</p>

\[\begin{equation}
    \pi_k^{\star}(\boldsymbol{e}) = f_k^{\star} \; g^{\star}(\boldsymbol{e})
\end{equation}\]

<p>All star points land on a line that runs through the (0, 0) point and the special disagreement point.
This line has slope 
\(f_2^{\star}/f_1^{\star}\) in share space,
which is equal to its slope in payoff space 
\(\pi_2^{\star}(\boldsymbol{e})/\pi_1^{\star}(\boldsymbol{e})\)
for all \(\boldsymbol{e}\).</p>

<p>When \(\delta = 1\),
the line in payoff space will run through the disagreement point,
which allows us to find the \(\boldsymbol{e}\) for which the shares do not vary with
\(\boldsymbol{r}\) (keeping ratios constant) nor with \(\delta\)</p>

\[\begin{align}
    \frac{e^{\star}_2}{e^{\star}_1} &amp;= \pi_2^{\star}(\boldsymbol{e})/\pi_1^{\star}(\boldsymbol{e}), \\
    &amp;= f_2^{\star}/f_1^{\star}, \\
    &amp;= 0.5, \\
    &amp; \implies (e_1^{\star}, e_2^{\star}) = (2/3, 1/3)
\end{align}\]

<p>This doesn’t help us find the \(f_i^*\) and \(\delta_{\text{min}}\);
finding that intersection requires finding the equation for the dotted line.
But it does explain when shares won’t change,
and helps us have an intuition about where the shares are “heading towards”
depending on \(\boldsymbol{e}\) relative to \(\boldsymbol{e}^{\star}\).
For a given \(\boldsymbol{e}\),
as \(\boldsymbol{r} \rightarrow \boldsymbol{r}^{\star}\),
\(\boldsymbol{f} \rightarrow \boldsymbol{f}^*\).</p>

<p>Let’s now look at the special disagreement point from another angle.
The trajectory of the disagreement point is defined by</p>

\[\begin{equation*}
    f_k = \frac{e_k}{e_k r_k + \delta \sum_{j \neq k} e_j r_j}.
\end{equation*}\]

<p>The special points occur when \(\delta = 0\), when</p>

\[\begin{equation*}
    f_k^{\bullet} = \frac{1}{r_k},
\end{equation*}\]

<p>which is then substituted into the payoff to get the special payoff points</p>

\[\begin{equation*}
    \pi_k^{\star} = f_k^{\bullet} \sum_i e_i r_i.
\end{equation*}\]

<p>Note, however,
that \(f_k^{\bullet}\) is independent of the \(\boldsymbol{e}\) values.
Therefore,
provided the \(r\)-ratios are kept constant,
all \(\boldsymbol{e}\) scenarios head towards the vector defined by
by the \(f_k^{\bullet}\) values, i.e.,</p>

\[\begin{equation}
    \boldsymbol{f}^{\bullet} = \left( \frac{1}{r_1}, \frac{1}{r_2}, \ldots, \frac{1}{r_n} \right).
\end{equation}\]

<p>When this is normalised,
we obtain the special disagreement point, \(\boldsymbol{e}\),
whose sharing rule is independent of the magnitude of the productivity vector, \(|\boldsymbol{r}|\).
It is the endowment point for which the dotted-line trajectory is straight (coincides with grey line).
The special endowment values are</p>

\[\begin{equation}
    e_i^{\star} = \frac{1}{r_i \sum_{j} \frac{1}{r_j}}.
\end{equation}\]

<p>Interpretation:
as the magnitude of productivies increases,
the solution heads towards the egalitarian solution to the special-endowment-value game
with social indices in proportion to the players’ productivities 
(less productive players have a higher social index).</p>

<h3 id="references">References</h3>

<p>Meyer, C. D. (2000). Matrix analysis and applied linear algebra, Society for Industrial and Applied Mathematics, Philadelphia.</p>

<p>Staab, M., Kleshnina, M., Hübner, V., Hilbe, C. and Chatterjee, K. (2022). Optimal sharing in social
dilemmas. URL: <a href="http://manuelstaab.com/research/optimal sharing soc dilemma.pdf">http://manuelstaab.com/research/optimal sharing soc dilemma.pdf</a></p>]]></content><author><name>nadiah_kristensen</name></author><category term="cooperation" /><summary type="html"><![CDATA[In an unpublished paper, Staab et al. (2022) studied the effects of heterogeneity in endowments and productivities on cooperation. They were particularly interested in the effect of allowing players to share the jointly produced goods unequally. Surprisingly, they found that unequal sharing can promote cooperation.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://nadiah.org/wp-content/uploads/2025/06/example_1.png" /><media:content medium="image" url="https://nadiah.org/wp-content/uploads/2025/06/example_1.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Evolutionary game theory virtual workshop</title><link href="https://nadiah.org/2025/05/08/online-evol-gt-workshop" rel="alternate" type="text/html" title="Evolutionary game theory virtual workshop" /><published>2025-05-08T04:44:54+00:00</published><updated>2025-05-08T04:44:54+00:00</updated><id>https://nadiah.org/2025/05/08/online-evol-gt-workshop</id><content type="html" xml:base="https://nadiah.org/2025/05/08/online-evol-gt-workshop"><![CDATA[<p>This morning,
I attended an engaging virtual workshop on modelling and applications of evolutionary game theory.</p>

<p>I extend my sincere thanks to the organisers –
<a href="https://datascience.unc.edu/person/alex-mcavoy/">Alex McAvoy</a>,
<a href="https://www.brynmawr.edu/inside/people/olivia-j-chu">Olivia Chu</a>,
and <a href="https://publish.illinois.edu/danielbcooney/">Daniel Cooney</a>
(<a href="https://bsky.app/profile/danielcooney1.bsky.social">bsky</a>)
– for their kind invitation and organising the workshop.
The presentations in my session were all very interesting.</p>

<h3>Maria Kleshnina's talk</h3>

<p><a href="https://sites.google.com/view/mklesh/home">Maria Kleshnina</a>
presented an overview of her recent work on iterated games.
I’m particularly intrigued by her model where player behaviour receives feedback from the environment, 
which I’ve previously discussed on my blog <a href="https://nadiah.org/2024/11/20/kleshnina_2023">here</a>.
She also discussed the concept of endowment distributions and 
sharing rules that are resilient to variation in the continuation probability.
I’ve recently been studying the latter through
<a href="http://manuelstaab.com/research/optimal_sharing_soc_dilemma.pdf">her manuscript</a>.</p>

<h3>Anuraag Bukkuri's talk</h3>

<p><a href="https://sites.google.com/view/anuraag-bukkuri/research">Anuraag Bukkuri</a>
presented a model for cancer-cell dynamics using a stage-structured adaptive dynamic approach.
While I’ve done <a href="https://nadiah.org/category/evolutionary_ecology">a little work</a>
using the adaptive-dynamics framework,
my focus has been on traditional eco-evolutionary applications.
I had no idea that it could also be used to design more effective cancer treatments,
which was a wonderful thing to learn.</p>

<h3>Dini Wang's talk</h3>

<p>Dini Wang 
(<a href="https://github.com/diniwang">Github</a>, 
<a href="https://arxiv.org/pdf/2404.03305">manuscript presented</a>)
presented an exploration of evolutionary games on hypergraphs.
While there’s extensive literature studying the evolution of cooperation on networks, 
hypergraphs have received comparatively less attention.
A hypergraph extends the concept of a simple graph: 
rather than players being connected solely through pairwise edges, 
they form potentially overlapping groups. 
These group relationships can be represented by hyperedges,
which are edges capable of connecting more than two nodes simultaneously.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2025/05/dini_wang_hypergraph.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2025/05/dini_wang_hypergraph.png" alt="An example of a hypergraph, taken from Dini's manuscript.  The nodes numbered 1-9 are players, and their group-connection is indicated by the shaded regions." />
    </a>
    
    <figcaption><span><p>An example of a hypergraph, taken from Dini’s manuscript.  The nodes numbered 1-9 are players, and their group-connection is indicated by the shaded regions.</p>
</span></figcaption>
</figure>

<p>The hypergraph structure influences both the payoffs players receive and the strategy-update process in evolutionary dynamics.
For example,
if you belong to two groups, you participate in two games, with your overall payoff being a combination from both games.
Hypergraphs also allow for more possibilities in the strategy-updating process than a regular graph.
For example,
in the social-learning dynamics,
when choosing which high-payoff individual to imitate,
one might first select an outstanding group
and then randomly select an individual from within this group.</p>

<p>Dini comprehensively explored different combinations of update rules 
made possible by the hypergraph structure,
and she presented findings on which structures promoted cooperation and which updating rules were most effective
(details in <a href="https://arxiv.org/pdf/2404.03305">her manuscript</a>).
I was particularly struck by her discovery that, compared to simple graphs, 
the higher-order interactions facilitated by hypergraph structures are more conducive to cooperation. 
This was my first introduction to hypergraphs, 
and they appear to offer a more natural framework for describing human interactions than pairwise connections, 
so I’m excited to read her manuscript in more detail.</p>

<h3>Saptarshi Pal's talk</h3>

<p>Finally,
<a href="https://www.math.harvard.edu/people/pal-saptarshi/">Saptarshi Pal</a>
presented on different conceptualisations of resilience
in subgame perfect Nash equilibria
as they relate to symmetric 2-player, 2-strategy iterated games.
His work focused on infinite iteration (continuation probability \(\delta = 1\))
and memory-1 strategies.
All of these concepts are new territory for me,
which means my interpretations of them below are likely wrong —
please ask him for his manuscript to be sure.</p>

<p>The first concept was the resilient strategy,
which strengthens the notion of a SPNE.
When both players adopt such a strategy,
at no point in the game does any player have an incentive to deviate.
Or, to put it another way,
the strategy is independent of the history that has occurred up to this point,
and the condition being met is robust to variation in that history.</p>

<p>The second concept was the stably resilient strategy,
which maintains its resilience even when the game’s payoffs are perturbed.
This can be viewed as a strategy that remains a best response even when players face uncertainty about exact payoffs.
Interestingly, it appears that virtually every resilient strategy is also stably resilient, 
and the set of stably resilient strategies consists of just four types: 
self-cooperators, self-defectors, and two forms of alternators.</p>

<p>The final concept was the most intriguing — the prediction-proof strategy —
which is a strategy that remains resilient even against opponents who can anticipate the player’s next move.
While “seeing the future” might seem far-fetched,
Saptarshi provided the real-world example of tennis champion Andre Agassi, 
who gained an advantage over Boris Becker by noticing that Becker 
unconsciously signalled the direction of his serves with his tongue. 
It seems likely we broadcast many signals about our intentions, both consciously and unconsciously.</p>

<p>As a concrete example,
consider the coordination game with payoffs \((R, S, T, P) = (1, 0, 0, 1)\),
where everyone ideally wants to play CC or DD.
Flipping a coin is a resilient strategy if one’s partner only looks at history.
However, if they have a strategy that conditions on your future move,
then their deviation payoff is higher (1 &gt; 1/2),
and so this SPNE is not a prediction-proof strategy.
The concept of a prediction-proof strategy is also applicable to 
situations where the stage game is not simultaneous
because the follower “predicts” what the leader has done
by having already seen it.</p>

<p>Saptarshi showed that these strategy types form a nested hierarchy:
stably resilient \(\subseteq\) prediction proof \(\subseteq\) resilient \(\subseteq\) SPNE.
In addition,
the stably resilient strategies (or one of them?) are socially optimal,
having the maximum total payoffs of all memory-1 games.
This clean result allows for a clear definition of the optimal strategy. 
Saptarshi plans to extend this work to scenarios where \(\delta &lt; 1\).</p>

<h3>My talk</h3>

<p>I presented an overview of my 
<a href="https://doi.org/10.1038/s41598-022-24590-y">recent</a>
<a href="https://nadiah.org/wp-content/uploads/2025/03/Kristensen25-Many_strategy_group_games_with_relatives_and_coordinated_cooperation.pdf">work</a>
developing mathematical techniques for analysing the evolution of cooperation 
in group games with family members.
My slides are available <a href="/wp-content/uploads/2025/05/evoln_coopn.pdf">here</a>.</p>]]></content><author><name>nadiah_kristensen</name></author><category term="cooperation" /><summary type="html"><![CDATA[This morning, I attended an engaging virtual workshop on modelling and applications of evolutionary game theory.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://nadiah.org/wp-content/uploads/2025/05/dini_wang_hypergraph.png" /><media:content medium="image" url="https://nadiah.org/wp-content/uploads/2025/05/dini_wang_hypergraph.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Predicting the effects of species eradication from the community matrix alone</title><link href="https://nadiah.org/2025/03/01/eradication" rel="alternate" type="text/html" title="Predicting the effects of species eradication from the community matrix alone" /><published>2025-03-01T04:44:54+00:00</published><updated>2025-03-01T04:44:54+00:00</updated><id>https://nadiah.org/2025/03/01/eradication</id><content type="html" xml:base="https://nadiah.org/2025/03/01/eradication"><![CDATA[<p>This blog post summarises one of the results I obtained from some work I did 
back in 2015 with Eve McDonald-Madden at the University of Queensland.
She didn’t end up using it for anything, but perhaps someone else might find it useful.</p>

<p>Modellers using the Qualitative Modelling approach often model a species eradication as a press-perturbation.
This is obviously wrong, but it’s convenient and has a certain intuitive appeal, 
and it’s not known how often it gives answers that are wrong enough to matter.</p>

<p>In this blog post,
I provide a fast method to predict the effects of an eradication using the community matrix alone.
A worked example is provided at the end.
The key assumption is that the underlying species dynamics are Lotka-Volterra.</p>

<p>This blog post might provide a starting point for investigating how the predictions of modelling a press-perturbation differ from eradication-proper, and for determining when it matters.</p>

<h2 id="background">Background</h2>

<h3 id="the-lotka-volterra-system">The Lotka-Volterra system</h3>

<p>Consider an ecosystem with \(S\) species, and denote the population size of species \(i\) at time \(t\) by \(n_{t;i}\).</p>

<p>We assume the system can be approximated by Lotka-Volterra dynamics</p>

\[\dot{n}_{t;i} = \frac{d n_{t;i}}{dt} 
= n_{t;i} \left( r_i + \sum_j b_{i,j} \; n_{t;j} \right), 
\qquad \forall i, j \in \{1, 2, \ldots, S \}, \tag{1}\]

<p>where \(r_i\) is the intrinsic growth rate of species \(i\) and \(b_{i,j}\) is the interaction strength between species \(i\) and \(j\). If species \(j\) has a negative effect on species \(i\), then \(b_{i,j} &lt; 0\), otherwise \(b_{i,j} &gt; 0\), and \(b_{i,j} = 0\) indicates that there is no direct interaction between the species.</p>

<p>The coefficients can be written in vector and matrix form; \(r = (r_1, r_2, \ldots, r_S)\) and \(B_{i,j} = [b_{i,j}]\). Then the dynamics described by Eq. 1 has a single steady state \(n = (n_1, n_2, \ldots, n_S)\), which is solved by</p>

\[n = B^{-1} \; (-r). \tag{2}\]

<p>The stability of the steady state is determined by investigating the eigenvalues of the Jacobian matrix</p>

\[J = \left[ \left. \frac{\partial \dot{n}_{t;i}}{\partial n_{t;j}} \right|_{n} \right] = N \; B, \tag{3}\]

<p>where \(N\) is the diagonal matrix formed of the vector of steady-state population sizes \(n\) and the partial derivates are evaluated at the steady state.
If the maximum real part of the eigenvalues is negative, then the steady state is locally stable.</p>

<h3 id="the-scaled-lotka-volterra-system">The scaled Lotka-Volterra system</h3>

<p>The dynamical system (Eq. 1) can be scaled with respect to the steady state.
Denote the scaled population sizes</p>

\[x_{t,i} \equiv \frac{n_{t;i}}{n_i}, \qquad \forall i \in \{1, 2, \ldots, S\}. \tag{4}\]

<p>Then the scaled system</p>

\[\begin{align}
\frac{1}{n_i} \; \frac{d n_{t;i}}{dt} 
&amp;= \frac{n_{t;i}}{n_i} \left( r_i + \sum_j b_{i,j} \; n_{t;j} \right), \\
\frac{d x_{t;i}}{dt} 
&amp;= x_{t;i} \left( r_i + \sum_j \underbrace{b_{i,j} \; n_j}_{a_{i,j}} \; x_{t;j} \right), 
\qquad \forall i, j \in \{1, 2, \ldots, S \}, \tag{5}
\end{align}\]

<p>with the new steady state \(x = (1, 1, \ldots, 1)\).</p>

<p>The new Jacobian matrix — also known as the ‘community matrix’ — is equal to the matrix of scaled interaction terms</p>

\[\left[ \left. \frac{\partial \dot{x}_{t;i}}{\partial x_{t;j}} \right|_{x} \right] = A = B\; N. \tag{6}\]

<p>Qualitative modelling typically starts with the community matrix \(A\),
which represents the dynamics linearised around the steady state.
In the case where the underlying dynamics are Lotka-Volterra,
the relationship between the original dynamical system and the scaled system has some noteworthy properties:</p>

<p>(1). Each \(A\) corresponds to a family of Lotka-Volterra systems with different \(B\) and \(n\) satisfying Eq. 6. This matters if you are uncertain about the parameter values but want to make some probabilistic predictive statements about how the system is likely to behave.
For example, if you want to say something “80% of the time, the predictions of the press-perturbation
match the eradication predictions”, then you need to think very carefully about this point (see: Kristensen et al. 2019).</p>

<p>(2). If it is assumed that all species are present in the system at steady state, i.e., \(n_i &gt; 0 \; \forall i\),
then the signs of the elements of \(A\) correspond to the signs of the direct interactions between species.</p>

<p>(3). Substituting Eq. 3 into Eq. 6, we obtain</p>

\[A = N^{-1} \; J \; N, \tag{6.5}\]

<p>which is a similarity transform.
Therefore, the eigenvalues of \(J\) are equal to the eigenvalues of \(A\).</p>

<p>(4). At the steady state</p>

\[r_i = \sum_j b_{i,j} \; n_j = -\sum_{j} a_{i,j}, \tag{7}\]

<p>so the signs of the intrinsic growth rates can also be determined from \(A\). This might be useful for parameterising the system. For example, if there is a predator species being modelled that has no prey items outside the model, then its growth rate should be negative.</p>

<h2 id="the-effect-of-species-eradication">The effect of species eradication</h2>

<p>Qualitative modelling often looks at the effects of a constant outflow (or inflow) of a species on the other species in the ecosystem. This is called a press perturbation, and modelling it involves investigating the signs of the elements of the inverse of \(A\) (Bender et al. 1984, Nakajima 1992). For example, Raymond et al. (2011) simulated the eradication of invasive species from Macquarie Island in this way.</p>

<p>However, a press perturbation of an invasive species is different from removing that species altogether. We have no reason to suppose the effect of removing a target species on other species will have the same signs, i.e., positive or negative effects on their population sizes, as removing a small (infinitesimally small!) number of the target species from the system at a constant rate.</p>

<p>Nonetheless, if we assume that the underlying dynamics of the system are Lotka-Volterra, there is another way we can know about the effects of an eradication using the community matrix \(A\) directly. <strong>In short, provided that no other species are sent extinct by the eradication, then we can predict both the proportional change in steady-state population sizes and the stability of the new steady state from the community matrix of the old system alone.</strong></p>

<h3 id="proportional-change-in-steady-state-population-sizes">Proportional change in steady-state population sizes</h3>

<p>Consider the eradication of species \(k\). Let us use subscript \(\ell \neq k\) to denote a vector or matrix modified by removing the \(k\)-th row or column as applicable. 
Let us denote the steady-state population sizes in the old system by \(n\) as before.
Let us denote the <strong>p</strong>ost-eradication steady-state population sizes by \(p = (p_1, p_2, \ldots, p_{k-1}, p_{k+1}, p_S)\),
which is a vector of length \(S-1\). 
We are interested in the proportional change in the population size of species \(i\) due to the removal of species \(k\),
which is defined as</p>

\[\delta_i \equiv \frac{p_i - n_i}{n_i}. \tag{8}\]

<p>In the old-system, the steady-state population sizes of the species who are not eradicated, \(n_{i \neq k}\), is our baseline of comparison.
Eq. 2 can be rewritten</p>

\[-r_{i \neq k} = B_{i \neq k, j \neq k} \; n_{i \neq k} + B_{i \neq k, k} \; n_k, \tag{9}\]

<p>and so</p>

\[n_{i \neq k} = (B_{i \neq k, j \neq k})^{-1} \; (-r_{i \neq k} - n_k \; B_{i \neq k, k}). \tag{10}\]

<p>Note that to calculate \((B_{i \neq k, j \neq k})^{-1}\) in Eq. 10, the rows and columns are removed first before the inversion is performed, and the same applies to all other bracketed inversions below.</p>

<p>At the new steady-state post-eradication, the steady-state population sizes of the other species is solved by</p>

\[p = (B_{i \neq k, j \neq k})^{-1} \; (-r_{i \neq k}). \tag{11}\]

<p>Therefore, the change in the steady-state population sizes of the other species due to eradication of species \(k\) is</p>

\[\begin{align}
p - n_{i \neq k} &amp;= (B_{i \neq k, j \neq k})^{-1} \; n_k \; B_{i \neq k, k}, \\
p - n_{i \neq k} &amp;= (B_{i \neq k, j \neq k})^{-1} \; A_{i \neq k, k}, \\
B_{i \neq k, j \neq k} \; (p - n_{i \neq k}) &amp;=  A_{i \neq k, k}. \tag{12}
\end{align}\]

<p>From Eq. 6, we have</p>

\[\begin{align}
B &amp;= A \; N^{-1}, \\
B_{i \neq k, j \neq k} &amp;= A_{i \neq k, j \neq k} \; (N^{-1})_{i \neq k, j \neq k}, \\
B_{i \neq k, j \neq k} &amp;= A_{i \neq k, j \neq k} \; (N_{i \neq k, j \neq k})^{-1}. \tag{13}
\end{align}\]

<p>The final step above is possible because \(N\) is a diagonal matrix,
and so its inverse is also a diagonal matrix whose nonzero elements are reciprocal to the original,
i.e., \(n_{i,i} \rightarrow 1/n_{i,i}\).
Therefore, 
\((N^{-1})_{i \neq k, j \neq k} = (N_{i \neq k, j \neq k})^{-1}\).
Let us denote the inverse submatrix of \(N\) by simply \(N_{i \neq k, j \neq k}^{-1}\) for brevity 
and to remind us of this fact.</p>

<p>Substituting Eq. 13 into Eq. 12
\(\begin{align}
A_{i \neq k, j \neq k} \; N_{i \neq k, j \neq k}^{-1} \; (p - n_{i \neq k}) &amp;=  A_{i \neq k, k}, \\
p - n_{i \neq k} &amp;= N_{i \neq k, j \neq k} \; (A_{i \neq k, j \neq k})^{-1} \; A_{i \neq k, k}. \tag{14}
\end{align}\)</p>

<p>Substituting Eq. 14 into Eq. 8,
we discover that the effect of eradication of species \(k\) on the steady-state population sizes of the other species
can be determined from the old community matrix alone</p>

\[\delta = (A_{i \neq k, j \neq k})^{-1} \; A_{i \neq k, k}. \tag{15}\]

<p>In the situation where multiple species are eradicated at once,
denote the set of eradicated-species’ indices \(K\),
then the result can be generalised to</p>

\[\delta_i = \left[ (A_{i \notin K, j \notin K})^{-1} \; \sum_{k \in K} A_{i \notin K, k}\right]_i.\]

<h3 id="stability-of-the-new-steady-state">Stability of the new steady state</h3>

<p>Eq. 15 tells us about how the new steady-state population sizes differ from the old ones,
but we might also be interested in whether the new steady state is stable.
For that, we need to investigate the Jacobians of the new system.</p>

<p>Let us denote the new Jacobian and new scaled community matrices by \(\hat{J}\) and \(\hat{A}\),
respectively.
Let us derive the new \(\hat{A}\), 
and let us also check \(\hat{A}\) has a similarity-transform relationship with \(\hat{J}\),
like in Eq. 6.5, which will ensure that the eigenvalues of \(\hat{A}\) are equal to the eigenvalues of \(\hat{J}\).</p>

<p>The elements of the new post-eradication Jacobian are</p>

\[\hat{J}_{i,j} = p_i \; b_{i,j} = n_i \; b_{i,j} \; (\delta_i + 1) = J_{i, j} \; (\delta_i +1),\]

<p>and so the new Jacobian is related to the old Jacobian by</p>

\[\hat{J} = \text{diag}(\delta +1) \; J_{i \neq k, j \neq k}. \tag{16}\]

<p>The elements of the new scaled Jacobian / community matrix are</p>

\[\hat{A}_{i,j} = b_{i,j} \; p_j = b_{i,j} \; n_j \; (\delta_i + 1) = A_{i,j} (\delta_i + 1),\]

<p>and so the new community matrix is related to the old community matrix by</p>

\[\hat{A} = A_{i \neq k, j \neq k} \; \text{diag}(\delta +1). \tag{17}\]

<p>Can we use \(\hat{A}\) to calculate the eigenvalues of \(\hat{J}\)?
We observe
\(\begin{align}
\hat{A} &amp;= A_{i \neq k, j \neq k} \; \text{diag}(\delta + 1), \\
&amp;= N^{-1}_{i \neq k, j \neq k} \; J_{i \neq k, j \neq k} \; N_{i \neq k, j \neq k} \; \text{diag}(\delta + 1), \\
&amp;= N^{-1}_{i \neq k, j \neq k} \; \text{diag}(\delta + 1)^{-1} \; \hat{J}  \; N_{i \neq k, j \neq k} \; \text{diag}(\delta + 1), \\
&amp;= \underbrace{N^{-1}_{i \neq k, j \neq k} \; \text{diag}(\delta + 1)^{-1}}_{X^{-1}} \; \hat{J}  \; \underbrace{\text{diag}(\delta + 1) \; N_{i \neq k, j \neq k}}_{X}, \tag{18}
\end{align}\)</p>

<p>which is a similarity transform. Therefore, the eigenvalues of \(\hat{J}\) are equal to the eigenvalues of \(\hat{A}\).
Furthermore,
because we can determine the new community matrix \(\hat{A}\) from the old community matrix \(A\) alone (Eq. 17),
then we can determine the stability of the new system from old community matrix alone.</p>

<h3 id="comments-on-the-method">Comments on the method</h3>

<h4 id="when-a-species-eradication-causes-another-species-to-go-extinct">When a species eradication causes another species to go extinct</h4>

<p>Eq. 15 gives the proportional change in the steady state,
and if \(\delta_i &lt; -1\),
then species \(i\)’s new steady-state population size is negative.
The real system cannot have species with negative population sizes — the least size is zero — and so Eq. 11 no longer applies.</p>

<p>What should we do in such a situation? 
The most rigorous way to determine the outcome would be to track the population dynamics.
However, we might also expect that negative species are the species that went extinct,
and so we could re-use maths above to study a system with those species removed.
Whether or not this expectation is generally correct, however, is something we might want to check.</p>

<p>What happens when species go extinct in the press-perturbation analysis?
In fact, the press-perturbation analysis overlooks this possibility entirely.
The press-perturbation analysis does not simulate eradications (though it’s often used for that purpose),
but instead simulates a continuous removal of an infinitesimally small number of the target species.
When only an infinitesimally small number of individuals are removed (or added),
then only infinitesimally small changes in the population sizes of other species result,
and no species ever goes extinct.</p>

<h4 id="scaling">Scaling</h4>

<p>Qualitative modelling often involves Monte Carlo simulations,
which means randomly sampling the magnitudes of the elements of \(A\) from some bounded range according to some distribution, e.g., a uniform distribution \(a_{i,j} \sim U(0, 1)\), and looking at the distribution of outcomes.
Therefore, we might be concerned about whether or not limiting the magnitudes of \(A\) to some bounded range 
affects the outcomes we’re sampling.</p>

<p>Consider some scaling factor \(\alpha\) that has been applied to the true community matrix \(T\), 
i.e., \(A = \alpha \; T\), which was done in order to guarantee that \(|a_{i,j}| \in (0, 1)\) for all \(i\), \(j\).
We know the signs of the eigenvalues will be preserved, \(\text{eig}(\alpha \; T) = \alpha \; \text{eig}(T)\),
so the stability analysis will be preserved. 
Happily, we can also show the proportional change in steady-state population sizes due to eradications will also be preserved.</p>

<p>Specifically</p>

\[\delta_i = \left[ \left( \alpha \; T_{i \notin K, j \notin K}\right)^{-1} \sum_{k \in K} \alpha \; T_{i \notin K, k} \right]_i
= 
\left[ \left(T_{i \notin K, j \notin K}\right)^{-1} \sum_{k \in K} T_{i \notin K, k} \right]_i,\]

<p>so \(\delta_i\) is not affected by the scaling factor.</p>

<h2 id="worked-example">Worked example</h2>

<p>The purpose of this example is to verify the maths above and provide a starting point for future analysis.</p>

<p>Let us consider the classic apex-predator + mesopredator + prey system,
depicted in Figure 1.
This system is particularly important to invasive-species eradication,
e.g., cats + rats + native species,
because conservation interventions to eradicate the apex-predator species (e.g., cats) can cause an increase in the mesopredator abundance (e.g., rats) and lead to worse outcomes for the prey (e.g., native species) than if nothing had been done.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2025/03/figure_1.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2025/03/figure_1.png" alt="Figure 1. Example ecosystem" />
    </a>
    
    <figcaption><span><p>Figure 1. Example ecosystem</p>
</span></figcaption>
</figure>

<p>Label the three species:</p>
<ol>
  <li>apex predator</li>
  <li>mesopredator</li>
  <li>prey</li>
</ol>

<p>and let us assume the system obeys Lotka-Volterra dynamics with coefficience \(r\) and \(B\) defined below.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Code is written in Python
</span>
<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>

<span class="c1"># number of species
</span><span class="n">S</span> <span class="o">=</span> <span class="mi">3</span>

<span class="c1"># intrinsic growth rates
</span><span class="n">r</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">([</span><span class="o">-</span><span class="mf">0.09868652</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.10491318</span><span class="p">,</span>  <span class="mf">1.24977311</span><span class="p">])</span>

<span class="c1"># Lotka-Volterra interaction terms
</span><span class="n">B</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">(</span>
    <span class="p">[</span>
        <span class="p">[</span><span class="o">-</span><span class="mf">8.28410720e-03</span><span class="p">,</span>  <span class="mf">2.81792972e-03</span><span class="p">,</span>  <span class="mf">1.11307405e-02</span><span class="p">],</span>
        <span class="p">[</span><span class="o">-</span><span class="mf">4.39125000e-05</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.12271716e-03</span><span class="p">,</span>  <span class="mf">1.94991860e-02</span><span class="p">],</span>
        <span class="p">[</span><span class="o">-</span><span class="mf">6.15708830e-03</span><span class="p">,</span> <span class="o">-</span><span class="mf">5.27941320e-04</span><span class="p">,</span> <span class="o">-</span><span class="mf">2.51039475e-02</span><span class="p">]</span>
    <span class="p">]</span>
<span class="p">)</span>
</code></pre></div></div>

<p>At the steady state, the population sizes are:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">n</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linalg</span><span class="p">.</span><span class="n">inv</span><span class="p">(</span><span class="n">B</span><span class="p">)</span> <span class="o">@</span> <span class="p">(</span><span class="o">-</span><span class="n">r</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[100. 250.  20.]
</code></pre></div></div>

<p>The Jacobian evaluated at the steady state:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">J</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">diag</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="o">@</span> <span class="n">B</span>
<span class="k">print</span><span class="p">(</span><span class="n">J</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[[-0.82841072  0.28179297  1.11307405]
 [-0.01097812 -0.28067929  4.8747965 ]
 [-0.12314177 -0.01055883 -0.50207895]]
</code></pre></div></div>

<p>The eigenvalues of the Jacobian:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">J_eigs</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linalg</span><span class="p">.</span><span class="n">eigvals</span><span class="p">(</span><span class="n">J</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">J_eigs</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[-0.99815469+0.j         -0.30650713+0.52456685j -0.30650713-0.52456685j]
</code></pre></div></div>
<p>The maximum real part of the eigenvalues is negative, so the steady state is stable.</p>

<p>Let’s check if the eigenvalues of \(A\) match \(J\).</p>

<p>The scaled community matrix \(A\):</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">A</span> <span class="o">=</span> <span class="n">B</span> <span class="o">@</span> <span class="n">np</span><span class="p">.</span><span class="n">diag</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">A</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[[-0.82841072  0.70448243  0.22261481]
 [-0.00439125 -0.28067929  0.38998372]
 [-0.61570883 -0.13198533 -0.50207895]]
</code></pre></div></div>

<p>\(A\)’s eigenvalues are</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">A_eigs</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linalg</span><span class="p">.</span><span class="n">eigvals</span><span class="p">(</span><span class="n">A</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">A_eigs</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[-0.99815469+0.j         -0.30650713+0.52456685j -0.30650713-0.52456685j]
</code></pre></div></div>

<p>So we have verified that \(A\) has the same eigenvalues as \(J\).</p>

<h3 id="check-that-scaling-the-dynamics-works">Check that scaling the dynamics works</h3>

<p>Let us verify that the dynamics described by \(B\) and by \(A\) are the same.</p>

<p>We will start both systems with population sizes 1/10 of their value at steady state,
and track how the population sizes change over time.</p>

<p>First, let’s do the original Lotka-Volterra system:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">scipy</span> <span class="kn">import</span> <span class="n">integrate</span>

<span class="c1"># define the original dynamics
</span><span class="k">def</span> <span class="nf">dn_dt</span><span class="p">(</span><span class="n">n_t</span><span class="p">,</span> <span class="n">t</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">n_t</span> <span class="o">*</span> <span class="p">(</span><span class="n">r</span> <span class="o">+</span> <span class="n">B</span> <span class="o">@</span> <span class="n">n_t</span><span class="p">)</span>

<span class="c1"># time range to plot the dynamics over
</span><span class="n">t</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linspace</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">100</span><span class="p">)</span>

<span class="c1"># initial values for the population sizes
</span><span class="n">n_t_0</span> <span class="o">=</span> <span class="mf">0.1</span> <span class="o">*</span> <span class="n">n</span>

<span class="c1"># integrate numerically
</span><span class="n">n_t</span><span class="p">,</span> <span class="n">infodict</span> <span class="o">=</span> <span class="n">integrate</span><span class="p">.</span><span class="n">odeint</span><span class="p">(</span><span class="n">dn_dt</span><span class="p">,</span> <span class="n">n_t_0</span><span class="p">,</span> <span class="n">t</span><span class="p">,</span> <span class="n">full_output</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">infodict</span><span class="p">[</span><span class="s">"message"</span><span class="p">])</span>

<span class="c1"># plot
</span><span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">n_t</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="p">[</span><span class="s">"apex"</span><span class="p">,</span> <span class="s">"meso"</span><span class="p">,</span> <span class="s">"prey"</span><span class="p">])</span>
<span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="s">'Using B'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">'Time'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">'Unscaled population sizes'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">legend</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="s">"best"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Integration successful.
</code></pre></div></div>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2025/03/using_B_unscaled.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2025/03/using_B_unscaled.png" alt="Figure 2. Ecosystem dynamics of the original, unscaled system." />
    </a>
    
    <figcaption><span><p>Figure 2. Ecosystem dynamics of the original, unscaled system.</p>
</span></figcaption>
</figure>

<p>We want to compare to scaled results, so let’s scale the plot by dividing through by the steady-state population sizes:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">n_t</span><span class="o">/</span><span class="n">n</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="p">[</span><span class="s">"apex"</span><span class="p">,</span> <span class="s">"meso"</span><span class="p">,</span> <span class="s">"prey"</span><span class="p">])</span>
<span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="s">'Using B'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">'Time'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">'Scaled population sizes'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">legend</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="s">"best"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2025/03/using_B_scaled.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2025/03/using_B_scaled.png" alt="Figure 3. A repeat of Fig. 2 but the population sizes have been scaled to their steady-state values." />
    </a>
    
    <figcaption><span><p>Figure 3. A repeat of Fig. 2 but the population sizes have been scaled to their steady-state values.</p>
</span></figcaption>
</figure>

<p>Now let’s do the dynamics of the scaled Lotka-Volterra system using the community matrix \(A\):</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># define the scaled dynamics
</span><span class="k">def</span> <span class="nf">dx_dt</span><span class="p">(</span><span class="n">x_t</span><span class="p">,</span> <span class="n">t</span><span class="o">=</span><span class="mi">0</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">x_t</span> <span class="o">*</span> <span class="p">(</span><span class="n">r</span> <span class="o">+</span> <span class="n">A</span> <span class="o">@</span> <span class="n">x_t</span><span class="p">)</span>

<span class="c1"># initial values for the population sizes
</span><span class="n">x_t_0</span> <span class="o">=</span> <span class="mf">0.1</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="n">ones</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>

<span class="c1"># integrate numerically
</span><span class="n">x_t</span><span class="p">,</span> <span class="n">infodict</span> <span class="o">=</span> <span class="n">integrate</span><span class="p">.</span><span class="n">odeint</span><span class="p">(</span><span class="n">dx_dt</span><span class="p">,</span> <span class="n">x_t_0</span><span class="p">,</span> <span class="n">t</span><span class="p">,</span> <span class="n">full_output</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">infodict</span><span class="p">[</span><span class="s">"message"</span><span class="p">])</span>

<span class="c1"># plot
</span><span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">x_t</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="p">[</span><span class="s">"apex"</span><span class="p">,</span> <span class="s">"meso"</span><span class="p">,</span> <span class="s">"prey"</span><span class="p">])</span>
<span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="s">'Using A'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">'Time'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">'Scaled population sizes'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">legend</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="s">"best"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Integration successful.
</code></pre></div></div>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2025/03/using_A.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2025/03/using_A.png" alt="Figure 3. Ecosystem dynamics using the scaled matrix A." />
    </a>
    
    <figcaption><span><p>Figure 3. Ecosystem dynamics using the scaled matrix A.</p>
</span></figcaption>
</figure>

<p>Figs. 2 and 3 are the same, which verifies that the scaling preserves the dynamics.</p>

<h3 id="check-method-for-species-eradication-works">Check method for species eradication works</h3>

<p>Let’s now check that our equation for \(\delta_i\) (Eq. 15) is correct. 
First, let’s remove a species in the original system and see how it changes the population steady states.
Then, let’s see if we can obtain the same proportional changes from the scaled community matrix alone.
Let us remove the apex predator, species 1.</p>

<p>Our old interaction matrix and intrinsic growth rates were:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">print</span><span class="p">(</span><span class="s">"B = "</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">B</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">r = "</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">r</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>B = 
[[-8.28410720e-03  2.81792972e-03  1.11307405e-02]
 [-4.39125000e-05 -1.12271716e-03  1.94991860e-02]
 [-6.15708830e-03 -5.27941320e-04 -2.51039475e-02]]

r = 
[-0.09868652 -0.10491318  1.24977311]
</code></pre></div></div>

<p>When we remove the apex predator, our \(B_{i \neq 1, j \neq 1}\) and \(r_{i \neq 1}\) will be:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">B_rem</span> <span class="o">=</span> <span class="n">B</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="n">S</span><span class="p">,</span> <span class="mi">1</span><span class="p">:</span><span class="n">S</span><span class="p">]</span> 
<span class="k">print</span><span class="p">(</span><span class="s">"new B = "</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">B_rem</span><span class="p">)</span>

<span class="c1"># Note: Python indexes elements from 0, while Matlab indexes from 1.
# If you were to do this in Matlab, you'd do something like
#   B_rem = B(2:S, 2:S)
</span>
<span class="n">r_rem</span> <span class="o">=</span> <span class="n">r</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="n">S</span><span class="p">]</span>
<span class="k">print</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">new r = "</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">r_rem</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>new B = 
[[-0.00112272  0.01949919]
 [-0.00052794 -0.02510395]]

new r = 
[-0.10491318  1.24977311]
</code></pre></div></div>

<p>Therefore, the new population steady state is:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">p</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linalg</span><span class="p">.</span><span class="n">inv</span><span class="p">(</span><span class="n">B_rem</span><span class="p">)</span> <span class="o">@</span> <span class="p">(</span><span class="o">-</span><span class="n">r_rem</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">p</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[564.8740935   37.90450627]
</code></pre></div></div>

<p>And the proportional changes in the other species steady-state population sizes is:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">delta</span> <span class="o">=</span> <span class="p">(</span><span class="n">p</span> <span class="o">-</span> <span class="n">n</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="n">S</span><span class="p">])</span> <span class="o">/</span> <span class="n">n</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="n">S</span><span class="p">]</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Proportional change from original Lotka Volterra equations:"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">delta</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Proportional change from original Lotka Volterra equations:
[1.25949637 0.89522531]
</code></pre></div></div>

<p>We find that, when the apex predator is eradicated, both the meso-predator’s and prey’s population size increases.</p>

<p>Can we get the same \(\delta\) using Eq. 15 above? Let’s try.</p>

<p>Recall Eq. 15 says: \(\delta = (A_{i \neq k, j \neq k})^{-1} \; A_{i \neq k, k}.\)</p>

<p>Recall our \(A\) was:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">print</span><span class="p">(</span><span class="n">A</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[[-0.82841072  0.70448243  0.22261481]
 [-0.00439125 -0.28067929  0.38998372]
 [-0.61570883 -0.13198533 -0.50207895]]
</code></pre></div></div>

<p>So \(A_{i \neq k, j \neq k}\) is:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">A_rem</span> <span class="o">=</span> <span class="n">A</span><span class="p">[</span><span class="mi">1</span><span class="p">:</span><span class="n">S</span><span class="p">,</span> <span class="mi">1</span><span class="p">:</span><span class="n">S</span><span class="p">]</span>
<span class="k">print</span><span class="p">(</span><span class="n">A_rem</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[[-0.28067929  0.38998372]
 [-0.13198533 -0.50207895]]
</code></pre></div></div>

<p>And \(A_{i \neq k, k}\) is:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">A_col</span> <span class="o">=</span> <span class="n">A</span><span class="p">[:,</span> <span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">:</span><span class="n">S</span><span class="p">]</span>
<span class="k">print</span><span class="p">(</span><span class="n">A_col</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[-0.00439125 -0.61570883]
</code></pre></div></div>

<p>Applying Eq. 15:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">delta_eq_15</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linalg</span><span class="p">.</span><span class="n">inv</span><span class="p">(</span><span class="n">A_rem</span><span class="p">)</span> <span class="o">@</span> <span class="n">A_col</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Proportional change from Eq 15:"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">delta</span><span class="p">)</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Proportional change from Eq 15:
[1.25949637 0.89522531]
</code></pre></div></div>

<p>Both methods give the same \(\delta\), which verifies Eq. 15 is correct.</p>

<h3 id="references">References</h3>

<p>Bender, E. A., Case, T. J. and Gilpin, M. E. (1984). Perturbation experiments in community ecology: theory and practice, Ecology 65(1): 1–13.</p>

<p>Kristensen, N.P., Chisholm, R.A., McDonald-Madden, E. (2019) Dealing with high uncertainty in qualitative network models using Boolean analysis, Methods in Ecology and Evolution 10: 1048-1061</p>

<p>Nakajima, H. (1992). Sensitivity and stability of flow networks, Ecological Modelling 62(1): 123–133.</p>

<p>Raymond, B., McInnes, J., Dambacher, J. M., Way, S. and Bergstrom, D. M. (2011). Qualitative modelling of invasive species eradication on subantarctic Macquarie Island, Journal of Applied Ecology 48(1): 181–191.</p>]]></content><author><name>nadiah_kristensen</name></author><category term="qualitative_modelling" /><summary type="html"><![CDATA[This blog post summarises one of the results I obtained from some work I did back in 2015 with Eve McDonald-Madden at the University of Queensland. She didn’t end up using it for anything, but perhaps someone else might find it useful.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://nadiah.org/wp-content/uploads/2025/03/figure_1.png" /><media:content medium="image" url="https://nadiah.org/wp-content/uploads/2025/03/figure_1.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Check if an iterated Prisoner’s Dilemma strategy is a subgame perfect Nash equilibrium</title><link href="https://nadiah.org/2024/11/20/kleshnina_2023" rel="alternate" type="text/html" title="Check if an iterated Prisoner’s Dilemma strategy is a subgame perfect Nash equilibrium" /><published>2024-11-20T04:44:54+00:00</published><updated>2024-11-20T04:44:54+00:00</updated><id>https://nadiah.org/2024/11/20/kleshnina_2023</id><content type="html" xml:base="https://nadiah.org/2024/11/20/kleshnina_2023"><![CDATA[<p>I recently read a paper by Kleshnina et al. (2023), 
<a href="https://www.nature.com/articles/s41467-023-39625-9.pdf">The effect of environmental information on evolution of cooperation in stochastic games</a>,
which provided an opportunity to teach myself about how to analyse iterated games.
In particular,
the problem they investigated admits 64 possible scenarios
with 256 possible strategies each,
and I was interested in writing code that could automate the analysis.
The solution I eventually landed on 
(<a href="https://github.com/nadiahpk/kleshnina-2023-playground">Github repo</a>)
used a combination of 
<a href="https://www.sympy.org/en/index.html">SymPy</a>,
<a href="https://networkx.org/">NetworkX</a>,
<a href="https://www.sagemath.org/">SageMath</a>,
the <a href="https://microsoft.github.io/z3guide/docs/logic/intro/">Z3 Theorem Prover</a>,
and <a href="https://pyeda.readthedocs.io/en/latest/">PyEDA</a> for Boolean minimisation,
but I think my approach could be improved.</p>

<h3 id="background-to-the-paper">Background to the paper</h3>

<p>Kleshnina et al. (2023) 
studied an iterated Prisoner’s Dilemma (PD) 
where the actions of the players affect the environment and visa versa.
Typically, if the environment is good,
the players receive higher payoffs,
but if some players defect,
then the environment transitions from good to bad.
They were interested in what effect knowledge of the environment’s state 
would have on cooperation outcomes.
Here, however, I will focus on one small aspect of the methodology:
how to identify if a strategy is a sub-game perfect Nash equilibrium and the conditions required.
This is the focus of Supplementary Note 3.</p>

<h4 id="game-structure">Game structure</h4>

<p>Two players are playing an iterated a Prisoner’s Dilemma (PD),
where in each round they can either cooperate (C) or defect (D).
The environment has two possible states, \(S = \{ s_1, s_2 \}\), 
or ‘good’ and ‘bad’, respectively.
The environment state determines payoffs according to the tables below,
where \(b_1 &gt; b_2 &gt; c\).</p>

<p>In the good environment:</p>

<table style="width: 200px; margin-left: auto; margin-right: auto;">
  <thead>
    <tr>
      <th style="text-align: center"> </th>
      <th style="text-align: center">C</th>
      <th style="text-align: center">D</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">C</td>
      <td style="text-align: center">\(b_1 - c\)</td>
      <td style="text-align: center">\(-c\)</td>
    </tr>
    <tr>
      <td style="text-align: center">D</td>
      <td style="text-align: center">\(b_1\)</td>
      <td style="text-align: center">\(0\)</td>
    </tr>
  </tbody>
</table>

<p>In the bad environment:</p>

<table style="width: 200px; margin-left: auto; margin-right: auto;">
  <thead>
    <tr>
      <th style="text-align: center"> </th>
      <th style="text-align: center">C</th>
      <th style="text-align: center">D</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">C</td>
      <td style="text-align: center">\(b_2 - c\)</td>
      <td style="text-align: center">\(-c\)</td>
    </tr>
    <tr>
      <td style="text-align: center">D</td>
      <td style="text-align: center">\(b_2\)</td>
      <td style="text-align: center">\(0\)</td>
    </tr>
  </tbody>
</table>

<h4 id="environment-transitions">Environment transitions</h4>

<p>I will focus on the scenario where the environment is deterministic
and changes state depending on its previous state and the players’ actions. 
In each round, the environment will either transition to the good state or the bad state.
To represent this mathematically, 
let \(q_{a, \overline{a}}^i\) be an indicator variable given the previous environment state \(i\) 
and player actions \(a, \overline{a}\). 
When \(q_{a, \overline{a}}^i = 1\), the environment will transition to the good state in the next round; when \(q_{a, \overline{a}}^i = 0\), it will transition to the bad state. 
The complete set of possible transitions can be represented by the vector:</p>

\[\boldsymbol{q} = ( q_{CC}^1, q_{CD}^1, q_{DD}^1; q_{CC}^2, q_{CD}^2, q_{DD}^2 ).\]

<p>I will also focus on the full-information setting,
where players actions depend on both
the current environment state as well as the players’ previous actions (memory-1).
Similar to the environment transitions, 
we use an indicator variable \(p_{a, \overline{a}}^j\) to represent a player’s decision in state \(j\) given previous actions \(a, \overline{a}\). 
Here, \(p_{a, \overline{a}}^j = 1\) indicates cooperation 
and \(p_{a, \overline{a}}^j = 0\) indicates defection. 
A player’s complete strategy can therefore be represented by the vector:</p>

\[\boldsymbol{p}_F = ( p_{CC}^1, p_{CD}^1, p_{DC}^1, p_{DD}^1; p_{CC}^2, p_{CD}^2, p_{DC}^2, p_{DD}^2 ).\]

<h4 id="player-strategies">Player strategies</h4>

<p>In Supplementary Note 3, Kleshnina et al. study what they call the ‘timeout’ game. 
In this game, if any player defects, both are penalised by spending one round in the bad environment. 
This mechanism can be represented by the transition vector:</p>

\[\boldsymbol{q} = (1, 0, 0; 1, 1, 1).\]

<p>They analyse several strategies in this setting, including one that combines two well-known approaches: playing ‘Grim’ in the good environment and ‘win-stay, lose-shift (WSLS)’ in the bad environment. This hybrid strategy is represented by the vector:</p>

\[\boldsymbol{p} = (1, 0, 0, 0; 1, 0, 0, 1)\]

<h4 id="one-shot-deviation-analysis">One-shot deviation analysis</h4>

<p>Kleshnina used the <a href="https://en.wikipedia.org/wiki/One-shot_deviation_principle">one-shot deviation principle</a>
to determine when the Grim-WSLS strategy in the timeout environment
is a sub-game perfect Nash equilibrium (SPNE).
Her analysis examines every possible combination of previous actions and current 
environment state (e.g., (CC, good), (CD, good), (DC, good), …, (DC, bad), (DD, bad)). 
For each combination, we calculate:</p>

<ul>
  <li>\(\pi_{a \overline{a}, i}\): the payoff for following the strategy</li>
  <li>\(\hat{\pi}_{a \overline{a}, i}\): the payoff for deviating from the strategy for one round</li>
  <li>\(\pi_{a \overline{a}, i} - \hat{\pi}_{a \overline{a}, i}\): the net benefit of staying with the strategy</li>
</ul>

<p>For the strategy to qualify as a SPNE, 
it must always be more profitable to stick with the strategy than to deviate from it 
for even a single round. Mathematically, this means:</p>

\[\pi_{a \overline{a}, i} - \hat{\pi}_{a \overline{a}, i} \geq 0 \text{ for all } (a \overline{a}, i).\]

<p>As an example,
consider the payoffs of the non-deviating and deviating players starting at CC in the good environment.
For the non-deviating case,
if both players start at CC, then they will cooperate in all subsequent rounds.
The payoff will be</p>

\[(b_1-c) + \delta(b_1-c) + \delta^2 (b_1-c) + \ldots,\]

<p>where \(\delta\) is the probability of playing another round.
Game theorists often use a \((1- \delta)\) normalisation to simplify the analysis. 
So the non-deviating payoff is calculated:</p>

\[\begin{align*}
    \pi_{\text{CC, good}} 
    &amp;= (1 - \delta) \left( (b_1-c) + \delta(b_1-c) + \delta^2 (b_1-c) + \ldots \right),\\
    &amp;= (b_1 - c) \underbrace{(1 - \delta) \left( 1 + \delta + \delta^2  + \ldots \right)}_{=1},  \\
    \pi_{\text{CC, good}} 
    &amp;= b_1 - c.
\end{align*}\]

<p>To calculate the one-shot deviation payoff,
we calculate what the payoff would be if
the focal player plays the opposite of what their strategy says for just one round,
i.e., instead of C they play D,
and the next round and then revert to their strategy.</p>

<p>The payoff in each round is tabulated below</p>

<table>
  <thead>
    <tr>
      <th style="text-align: right"> </th>
      <th style="text-align: center">0</th>
      <th style="text-align: center">1</th>
      <th style="text-align: center">2</th>
      <th style="text-align: center">3</th>
      <th style="text-align: center">4</th>
      <th style="text-align: center">5</th>
      <th style="text-align: center">…</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">state</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">2</td>
      <td style="text-align: center">1</td>
      <td style="text-align: center">…</td>
    </tr>
    <tr>
      <td style="text-align: right">player 1</td>
      <td style="text-align: center">C</td>
      <td style="text-align: center"><strong>D</strong></td>
      <td style="text-align: center">D</td>
      <td style="text-align: center">D</td>
      <td style="text-align: center">C</td>
      <td style="text-align: center">C</td>
      <td style="text-align: center">…</td>
    </tr>
    <tr>
      <td style="text-align: right">player 2</td>
      <td style="text-align: center">C</td>
      <td style="text-align: center">C</td>
      <td style="text-align: center">D</td>
      <td style="text-align: center">D</td>
      <td style="text-align: center">C</td>
      <td style="text-align: center">C</td>
      <td style="text-align: center">…</td>
    </tr>
    <tr>
      <td style="text-align: right">state transn</td>
      <td style="text-align: center"><strong>1</strong>00; 111</td>
      <td style="text-align: center">1<strong>0</strong>0; 111</td>
      <td style="text-align: center">10<strong>0</strong>; 111</td>
      <td style="text-align: center">100; 11<strong>1</strong></td>
      <td style="text-align: center"><strong>1</strong>00; 111</td>
      <td style="text-align: center"><strong>1</strong>00; 111</td>
      <td style="text-align: center">…</td>
    </tr>
    <tr>
      <td style="text-align: right">strat. transn</td>
      <td style="text-align: center"><strong>1</strong>000; 1001</td>
      <td style="text-align: center">1000; 10<strong>0</strong>1</td>
      <td style="text-align: center">1000; 100<strong>1</strong></td>
      <td style="text-align: center"><strong>1</strong>000; 1001</td>
      <td style="text-align: center"><strong>1</strong>000; 1001</td>
      <td style="text-align: center"><strong>1</strong>000; 1001</td>
      <td style="text-align: center">…</td>
    </tr>
    <tr>
      <td style="text-align: right">payoff</td>
      <td style="text-align: center"> </td>
      <td style="text-align: center">\(b_1\)</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">0</td>
      <td style="text-align: center">\(b_2 - c\)</td>
      <td style="text-align: center">\(b_1 - c\)</td>
      <td style="text-align: center">…</td>
    </tr>
  </tbody>
</table>

<p>The actual payoff is</p>

\[b_1 + \delta^3(b_2-c) + \delta^4( b_1 - c + \delta^1 (b_1 - c) + \ldots),\]

<p>which normalises to</p>

\[\hat{\pi}_{\text{CC, good}} = b_1(1-\delta) + \delta^3(b_2-c)(1-\delta) + \delta^4( b_1 - c ).\]

<p>Therefore, from the (CC, good) starting point, we obtain a condition for the strategy to be a SPNE</p>

\[\pi_{\text{CC, good}} - \hat{\pi}_{\text{CC, good}} 
= b_1 \delta (1 - \delta^3) - b_2 \delta^3 (1 - \delta) - c(1 - \delta^3)
\geq 0.
\label{1} \tag{1}\]

<p>To simplify the condition, Kleshnina et al. take the limit as \(\delta \rightarrow 1\),
which represents a kind of best-case scenario to identify a necessary condition.
First we pull out common \(1-\delta\) terms</p>

\[\pi_{\text{CC, good}} - \hat{\pi}_{\text{CC, good}} 
= (1 - \delta) \{
    b_1 \delta (1 + \delta + \delta^2) 
    - b_2 \delta^3 
    - c (1 + \delta + \delta^2)
\},\]

<p>and as \(\delta \rightarrow 1\), the condition becomes</p>

\[\lim_{\delta \rightarrow 1} (\pi_{\text{CC, good}} - \hat{\pi}_{\text{CC, good}})
= 3 b_1 - b_2 - 3 c \geq 0.
\label{2}
\tag{2}\]

<p>The analysis above is repeated for every possible combination (CC, good), (CD, good), …, (DD, bad).
Kleshnina et al. find that the condition is satisfied for all other combinations
except (DD, bad),
which, in a similar way to (CC, good) above, results in a condition</p>

\[\lim_{\delta \rightarrow 1} (\pi_{\text{DD, good}} - \hat{\pi}_{\text{DD, good}})
= 2 b_1 - b_2 - 2 c \geq 0.
\label{3}
\tag{3}\]

<p>Both of the conditions must be satisfied in order for the strategy to be a SPNE.
However, we can simplify the analysis by determining
relationship between the two conditions.
Eq. 2 can be rewritten \(b_1 - c + 2 b_1 - b_2 - 2 c \geq 0\).
Because \(b_1 &gt; c\),
we know that if Eq. 3 is satisfied, then Eq. 2 is satisfied.
Therefore,
Eq. 3 is the necessary condition for the strategy to be a SPNE.</p>

<h3 id="automating-the-analysis">Automating the analysis</h3>

<p>In the <a href="https://github.com/nadiahpk/kleshnina-2023-playground">kleshnina-2023-playground</a>
Github repository, 
I provide code that automates analysis above for an arbitrary 
environment \(\boldsymbol{q}\) and strategy \(\boldsymbol{p}\).
Below,
I will highlight some points of interests,
and I also note areas that I’m uncertain about / could be improved.</p>

<h4 id="encoding-the-environment-and-strategy-as-a-directed-graph">Encoding the environment and strategy as a directed graph</h4>

<p>I used NetworkX to encode every \((\boldsymbol{q}, \boldsymbol{p})\)
as a directed graph.
The nodes are the environment-state + action pairs,
e.g., (CC, good),
and the edges are the transitions between them.
Fig. 1 below shows the Timeout-Grim-WSLS example from above.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2024/11/timeout_grim_wsls.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2024/11/timeout_grim_wsls.png" alt="Figure 1. State transitions for the Grim-WSLS strategy played in the Timeout environment. Generated using `dottify_state_space()` in `fncs_general.py`." />
    </a>
    
    <figcaption><span><p>Figure 1. State transitions for the Grim-WSLS strategy played in the Timeout environment. Generated using <code class="language-plaintext highlighter-rouge">dottify_state_space()</code> in <code class="language-plaintext highlighter-rouge">fncs_general.py</code>.</p>
</span></figcaption>
</figure>

<h4 id="identifying-attractors">Identifying attractors</h4>

<p>In the Timeout-Grim-WSLS example above,
the state (CC, good) is a steady state – 
once reached,
players continue to cooperate and the environment remains good.
However, 
it’s also possible to have periodic states that cycle through multiple configurations.</p>

<p>Consider an environment</p>

\[\boldsymbol{q} = (1, 1, 0; 0, 0, 1),\]

<p>and the strategy</p>

\[\boldsymbol{p} = (1, 0, 0, 0; 0, 0, 1, 1).\]

<p>As illustrated in Fig. 2 below,
this system has three attractors:</p>
<ol>
  <li>A steady state or period-1 cycle: (CC, good) \(\rightarrow\) (CC, good) \(\rightarrow\) …</li>
  <li>A period-2 cycle: (CD, bad) \(\rightarrow\) (DC, bad) \(\rightarrow\) (CD, bad) \(\rightarrow\) …</li>
  <li>A period-3 cycle: (CC, bad) \(\rightarrow\) (DD, bad) \(\rightarrow\) (DD, good) \(\rightarrow\) (CC, bad) \(\rightarrow\) …</li>
</ol>

<p>We need to identify such cyles,
and we will find that the normalisation used above no longer applies.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2024/11/cycle_example.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2024/11/cycle_example.png" alt="Figure 2. An example scenario with attractors that are period-1, period-2, and period-3 cycles." />
    </a>
    
    <figcaption><span><p>Figure 2. An example scenario with attractors that are period-1, period-2, and period-3 cycles.</p>
</span></figcaption>
</figure>

<p>To identify cycles,
I used the <code class="language-plaintext highlighter-rouge">strongly_connected_components()</code> function from NetworkX.
A <a href="https://en.wikipedia.org/wiki/Strongly_connected_component">strongly connected component (SCC)</a>
is a subgraph of a directed graph that has a path in each direction between each pair of vertices
and is maximal with respect to this property.
This function returns multi-node cycles
plus SCCs of length 1.
SCCs of length 1 may either be a node with no incoming links
or a period-1 cycle.
This is implemented in the <code class="language-plaintext highlighter-rouge">find_cycles()</code> function in <code class="language-plaintext highlighter-rouge">fncs_general.py</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">find_cycles</span><span class="p">(</span><span class="n">G</span><span class="p">):</span>

    <span class="c1"># find the attractors using the strongly-connected components algorithm
</span>    <span class="n">sccs</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">nx</span><span class="p">.</span><span class="n">strongly_connected_components</span><span class="p">(</span><span class="n">G</span><span class="p">))</span>
    <span class="n">cycles</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">scc</span> <span class="ow">in</span> <span class="n">sccs</span><span class="p">:</span>
        <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">scc</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span>
            <span class="c1"># multi-node SCCs are always cycles
</span>            <span class="n">cycles</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">scc</span><span class="p">)</span>
        <span class="k">elif</span> <span class="nb">len</span><span class="p">(</span><span class="n">scc</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
            <span class="n">node</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">scc</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
            <span class="c1"># an SCC of size 1 is a steady-state iff it's a self-loop
</span>            <span class="k">if</span> <span class="n">G</span><span class="p">.</span><span class="n">has_edge</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">node</span><span class="p">):</span>
                <span class="n">cycles</span><span class="p">.</span><span class="n">append</span><span class="p">({</span><span class="n">node</span><span class="p">})</span>

    <span class="k">return</span> <span class="n">cycles</span>
</code></pre></div></div>

<p>Returning to our example in Fig. 2,
we find that the normalisation factor \((1 - \delta)\) no longer applies.
For example,
the unnormalised no-deviation payoff from (CD, good) is</p>

\[\begin{align*}
    \pi_{\text{CD, good}} 
    &amp;= \delta 0 + \delta^2(b_2 - c) + \delta^3 0 + \delta^4 0 + \ldots, \\
    &amp;= \delta \left( \frac{\delta (b_2 - c)}{1- \delta^3} \right). \\
\end{align*}\]

<p>Therefore,
when the payoffs are calculated in <code class="language-plaintext highlighter-rouge">calc_longterm_payoffs()</code> 
(in <code class="language-plaintext highlighter-rouge">fncs_general.py</code>),
we leave them in their unnormalised form and perform the simplifications
later when the conditions are evaluated (in SageMath, detailed below).</p>

<h4 id="calculating-the-no-deviation-and-deviation-payoffs">Calculating the no-deviation and deviation payoffs</h4>

<p>I encoded the payoffs using SymPy (<code class="language-plaintext highlighter-rouge">calc_one_shot_devns.py</code>),
and checked the conditions using SageMath 
(<code class="language-plaintext highlighter-rouge">define_variables_assumptions.sage</code> and <code class="language-plaintext highlighter-rouge">check_spne.sage</code>).
To replicate the analysis done in Supplementary Note 3,
I did the checking of the conditions incrementally,
proceeding to the next step if the previous result was ambiguous.</p>
<ol>
  <li>Check the SPNE condition directly: 
Can SageMath immediately discern the sign of 
\(\pi_{a \overline{a}, i} - \hat{\pi}_{a \overline{a}, i}\)?</li>
  <li>Use SageMath function <code class="language-plaintext highlighter-rouge">numerator_denominator()</code> to cancel the normalisation
factors. Can SageMath discern the sign of each?
    <ul>
      <li>Note that I’m uncertain about the robustness of this step because it performs the simplifications
 I need as kind of side-effect of creating the fractional form and splitting it.</li>
    </ul>
  </li>
  <li>Simplify by setting \(\delta = 1\). Can SageMath discern the sign of
\(\lim_{\delta \rightarrow 1} \pi_{a \overline{a}, i} - \hat{\pi}_{a \overline{a}, i}\)?</li>
  <li>Write the condition from Step 3 to a file.</li>
</ol>

<p>I found that SageMath had difficulty working in the \((b_1, b_2, c)\) parameter space,
so I shifted to an \((x, y, c)\) parameter space defined as</p>

\[\begin{align*}
    x &amp;= b_2 - c, \\
    y &amp;= b_1 - b_2,
\end{align*}\]

<p>with the constraints \(x, y &gt; 0\).</p>

<p>I don’t feel very confident about the SageMath implementation.
I made an attempt to rewrite it in Z3,
but I ran into difficulties because Z3 does not have an equivalent to SageMath’s 
<code class="language-plaintext highlighter-rouge">numerator_denominator()</code> function.
Perhaps I can use some combination of SymPy and Z3 for this step.
This is an area for future improvement.</p>

<p>For each \(\boldsymbol{q}\),
output was a CSV file (<code class="language-plaintext highlighter-rouge">one_shot_devns_q_[binary string].csv</code>):</p>

<table>
  <thead>
    <tr>
      <th>p</th>
      <th>not_SPNE</th>
      <th>gCC_satisfied?</th>
      <th>gCC_how_determined?</th>
      <th>gCC_required_non_negative</th>
      <th>…</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>_00000000</td>
      <td>False</td>
      <td>True</td>
      <td>directly</td>
      <td>c</td>
      <td>…</td>
    </tr>
    <tr>
      <td>_00000001</td>
      <td>True</td>
      <td>True</td>
      <td>directly</td>
      <td>(b2 - c)<em>d^2/(d^2 - 1) + c - (b2 - c)</em>d/(d^2 - 1)</td>
      <td>…</td>
    </tr>
    <tr>
      <td>…</td>
      <td>…</td>
      <td>…</td>
      <td>…</td>
      <td>…</td>
      <td>…</td>
    </tr>
  </tbody>
</table>

<h4 id="calculate-the-necessary-conditions">Calculate the necessary conditions</h4>

<p>For each strategy that had the potential to be a SPNE in environment \(\boldsymbol{q}\),
I used the Z3 Theorem Prover to determine which of the conditions
(arising from each (CC,good), (CD, good), etc.) were the necessary conditions.
In other words,
I am analysing the relationships between
the expressions arising from
the requirement \(\pi_{a \overline{a}, i} - \hat{\pi}_{a \overline{a}, i} \geq 0\),
such as the condition \(3 b_1 - b_2 - 3 c \geq 0\) in the Timeout-Grim-WSLS example.</p>

<p>For each strategy, the initial steps were:</p>

<p>First, identify all unique conditions using <code class="language-plaintext highlighter-rouge">are_equivalent()</code> in <code class="language-plaintext highlighter-rouge">fncs_z3_1.py</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">are_equivalent</span><span class="p">(</span><span class="n">condn_a</span><span class="p">,</span> <span class="n">condn_b</span><span class="p">):</span>
    <span class="s">"""
    Returns True if condn_a and condn_b and equivalent.
    """</span>

    <span class="n">s</span> <span class="o">=</span> <span class="n">Solver</span><span class="p">()</span>
    <span class="n">s</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">condn_a</span> <span class="o">!=</span> <span class="n">condn_b</span><span class="p">)</span>
    <span class="n">result</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">check</span><span class="p">()</span> <span class="o">==</span> <span class="n">unsat</span>

    <span class="k">return</span> <span class="n">result</span>
</code></pre></div></div>

<p>Second, check if the unique conditions are mutually satisfiable (in <code class="language-plaintext highlighter-rouge">calc_necess_condns.py</code>):</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># earlier in the code, specify assumptions on parameters
</span><span class="n">b1</span><span class="p">,</span> <span class="n">b2</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">d</span> <span class="o">=</span> <span class="n">Reals</span><span class="p">(</span><span class="s">"b1 b2 c d"</span><span class="p">)</span>
<span class="n">variables</span> <span class="o">=</span> <span class="p">[</span><span class="n">b1</span><span class="p">,</span> <span class="n">b2</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">d</span><span class="p">]</span>
<span class="n">assumptions</span> <span class="o">=</span> <span class="n">And</span><span class="p">(</span>
    <span class="n">d</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">,</span>
    <span class="n">d</span> <span class="o">&lt;=</span> <span class="mi">1</span><span class="p">,</span>
    <span class="n">c</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">,</span>  <span class="c1"># b1 ≥ b2 &gt; c
</span>    <span class="n">b2</span> <span class="o">&gt;</span> <span class="n">c</span><span class="p">,</span>
    <span class="n">b1</span> <span class="o">&gt;</span> <span class="n">b2</span><span class="p">,</span> <span class="c1"># NOTE I changed this from Maria's b1 &gt;= b2
</span><span class="p">)</span>

<span class="c1"># for each strategies unique conditions ...
</span>
<span class="c1"># check conditions' mutual satisfiability
</span><span class="n">s</span> <span class="o">=</span> <span class="n">Solver</span><span class="p">()</span>
<span class="n">s</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">assumptions</span><span class="p">)</span>
<span class="k">for</span> <span class="n">condn</span> <span class="ow">in</span> <span class="n">condns</span><span class="p">:</span>
    <span class="n">s</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">condn</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">check</span><span class="p">()</span>
</code></pre></div></div>

<p>If the conditions were mutually satisfiable,
I then identified the minimal set of conditions that were the necessary conditions for that strategy.
For each pair of conditions \(C_1\) and \(C_2\),
I used Z3’s <code class="language-plaintext highlighter-rouge">qe</code> Tactic to determine if \(C_1 \implies C_2\),
implemented in <code class="language-plaintext highlighter-rouge">check_implication()</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># in the script that calls the function, specify we're using 'qe' tactic
</span><span class="n">tactic</span> <span class="o">=</span> <span class="n">Tactic</span><span class="p">(</span><span class="s">"qe"</span><span class="p">)</span>

<span class="c1"># for each pair of conditions, call this function
</span><span class="k">def</span> <span class="nf">check_implication</span><span class="p">(</span><span class="n">condn_a</span><span class="p">,</span> <span class="n">condn_b</span><span class="p">,</span> <span class="n">assumptions</span><span class="p">,</span> <span class="n">variables</span><span class="p">,</span> <span class="n">tactic</span><span class="p">):</span>
    <span class="s">"""
    Returns True if condn_a implies condn_b under the given assumptions
    """</span>

    <span class="n">formula</span> <span class="o">=</span> <span class="n">ForAll</span><span class="p">(</span><span class="n">variables</span><span class="p">,</span> <span class="n">Implies</span><span class="p">(</span><span class="n">And</span><span class="p">(</span><span class="n">assumptions</span><span class="p">,</span> <span class="n">condn_a</span><span class="p">),</span> <span class="n">condn_b</span><span class="p">))</span>
    <span class="n">result</span> <span class="o">=</span> <span class="n">tactic</span><span class="p">.</span><span class="nb">apply</span><span class="p">(</span><span class="n">formula</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">result</span><span class="p">.</span><span class="n">as_expr</span><span class="p">()</span>
</code></pre></div></div>

<p>This allowed me to construction an implications matrix,
which has rows and columns corresponding to the conditions,
and an entry 1 indicates that \(C_i \implies C_j\).
The minimal set is the set of all conditions that are not implied by another condition,
i.e., the independent roots of the implications tree.</p>

<p>The results were stored in CSV files <code class="language-plaintext highlighter-rouge">necess_condns_q_[binary string].csv</code>.
The Timeout environment example (<code class="language-plaintext highlighter-rouge">necess_condns_q_100111.csv</code>)
is shown below.</p>

<table>
  <thead>
    <tr>
      <th>p</th>
      <th>simul_sat?</th>
      <th>necessary_conditions</th>
      <th>necessary_conditions_xy</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>_00000000</td>
      <td>True</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>_00001000</td>
      <td>True</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>_10000000</td>
      <td>True</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>_10000001</td>
      <td>True</td>
      <td>2<em>b1 - b2 - 2</em>c</td>
      <td>-1<em>c + x + 2</em>y</td>
    </tr>
    <tr>
      <td>_10000111</td>
      <td>True</td>
      <td>b1 - b2 - c</td>
      <td>y + -1*c</td>
    </tr>
    <tr>
      <td>_10001000</td>
      <td>True</td>
      <td> </td>
      <td> </td>
    </tr>
    <tr>
      <td>_10001001</td>
      <td>True</td>
      <td>2<em>b1 - b2 - 2</em>c</td>
      <td>-1<em>c + x + 2</em>y</td>
    </tr>
    <tr>
      <td>_10001111</td>
      <td>True</td>
      <td>b1 - b2 - c</td>
      <td>y + -1*c</td>
    </tr>
    <tr>
      <td>_10010000</td>
      <td>True</td>
      <td>b1 - 2*c</td>
      <td>-1*c + x + y</td>
    </tr>
    <tr>
      <td>_10010001</td>
      <td>True</td>
      <td>b1 - 2*c</td>
      <td>-1*c + x + y</td>
    </tr>
    <tr>
      <td>_10010110</td>
      <td>True</td>
      <td>b1 - b2 - c | -b2 + 2*c</td>
      <td>y + -1<em>c | c + -1</em>x</td>
    </tr>
    <tr>
      <td>_10010111</td>
      <td>True</td>
      <td>b1 - b2 - c</td>
      <td>y + -1*c</td>
    </tr>
    <tr>
      <td>_10011000</td>
      <td>True</td>
      <td>b1 - 2*c</td>
      <td>-1*c + x + y</td>
    </tr>
    <tr>
      <td>_10011001</td>
      <td>True</td>
      <td>b1 - 2*c</td>
      <td>-1*c + x + y</td>
    </tr>
    <tr>
      <td>_10011110</td>
      <td>True</td>
      <td>b1 - b2 - c | -b2 + 2*c</td>
      <td>y + -1<em>c | c + -1</em>x</td>
    </tr>
    <tr>
      <td>_10011111</td>
      <td>True</td>
      <td>b1 - b2 - c</td>
      <td>y + -1*c</td>
    </tr>
    <tr>
      <td>_11110000</td>
      <td>True</td>
      <td>b1 - 2*c</td>
      <td>-1*c + x + y</td>
    </tr>
    <tr>
      <td>_11111000</td>
      <td>True</td>
      <td>b1 - 2*c</td>
      <td>-1*c + x + y</td>
    </tr>
  </tbody>
</table>

<h4 id="grouping-same-condition-strategies">Grouping same-condition strategies</h4>

<p>Often,
different strategies \(\boldsymbol{p}\) have the same necessary conditions.
To further summarise the analysis,
I grouped all such \(\boldsymbol{p}\) together using PyEDA’s Boolean minimisation
on the set of \(\boldsymbol{p}\) binary strings.</p>

<p>For example,
in the Timeout environment,
the \(\boldsymbol{p}\) corresponding to the following 4 binary strings
have no additional conditions in order to be a SPNE:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>00000000
00001000
10000000
10001000
</code></pre></div></div>
<p>These four strings can be represented in a more compact form by a single string</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-000-000
</code></pre></div></div>
<p>where the <code class="language-plaintext highlighter-rouge">-</code> indicates that the strategy can either cooperate or defect
in that state.</p>

<p>The Boolean minimisation was implemented in <code class="language-plaintext highlighter-rouge">group_necess_condns.py</code>
and output was written to CSV files <code class="language-plaintext highlighter-rouge">necess_condns_grouped_q_[binary string].csv</code>.
The Timeout environment example (<code class="language-plaintext highlighter-rouge">necess_condns_grouped_q_100111.csv</code>)
is shown below.</p>

<table>
  <thead>
    <tr>
      <th>id</th>
      <th>necessary_conditions</th>
      <th>necessary_conditions_xy</th>
      <th>compact_ps</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td> </td>
      <td> </td>
      <td>_-000-000</td>
    </tr>
    <tr>
      <td>1</td>
      <td>2<em>b1 - b2 - 2</em>c</td>
      <td>-1<em>c + x + 2</em>y</td>
      <td>_1000-001</td>
    </tr>
    <tr>
      <td>2</td>
      <td>b1 - b2 - c</td>
      <td>y + -1*c</td>
      <td>_100–111</td>
    </tr>
    <tr>
      <td>3</td>
      <td>b1 - 2*c</td>
      <td>-1*c + x + y</td>
      <td>_1111-000 | _1001-00-</td>
    </tr>
    <tr>
      <td>4</td>
      <td>b1 - b2 - c | -b2 + 2*c</td>
      <td>y + -1<em>c | c + -1</em>x</td>
      <td>_1001-110</td>
    </tr>
  </tbody>
</table>

<p>I wrote scripts <code class="language-plaintext highlighter-rouge">plot_necess_condns.py</code>
and <code class="language-plaintext highlighter-rouge">plot_transn_graphs_pq.py</code> to visualise each
\(\boldsymbol{p}\) group and its necessary conditions.
I found it more convenient to work in the \((x, y)\) parameter space
for the conditions.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2024/11/timeout_wsls_analysis.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2024/11/timeout_wsls_analysis.png" alt="Figure 3. Summary analysis of Grim-WSLS and Grim-Risker strategy played in the Timeout environment. Panels show (a) the Timeout environment state transitions, (b) the state transitions that result from the combined Grim-WSLS and Grim-Risker strategies played in the Timeout environment, and (c) the region of parameter space (purple) that satisfies the necessary condition for the strategies to be a SPNE." />
    </a>
    
    <figcaption><span><p>Figure 3. Summary analysis of Grim-WSLS and Grim-Risker strategy played in the Timeout environment. Panels show (a) the Timeout environment state transitions, (b) the state transitions that result from the combined Grim-WSLS and Grim-Risker strategies played in the Timeout environment, and (c) the region of parameter space (purple) that satisfies the necessary condition for the strategies to be a SPNE.</p>
</span></figcaption>
</figure>

<h3 id="a-summary-of-all-spne-in-sensible-environments">A summary of all SPNE in sensible environments</h3>

<p>With code to automate the analysis,
it is possible to do a sweep of a range of scenarios quickly.
Below, I show the results I obtained for
all environments with a ‘sensible’ transition pattern,
i.e., that stay in or return to the good environment when both players cooperate
and stay or return to the good environment when both players defect.
These environments match the pattern</p>

\[\boldsymbol{q} = (1, -, 0; 1, -, 0).\]

<p>Figs. 4–6 below summarise the SPNE I found in each environmental scenario.</p>

<p>Environments are categorised based on how fragile they are
and their return conditions.
A fragile environment transitions from good to bad when any player defects,
but a resilient environment only transitions when both players defect.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2024/11/harsh.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2024/11/harsh.png" alt="Figure 4. SPNE in harsh conditional return environments.
    From left to right, top to bottom, 
    the $$\boldsymbol{p}$$-groups in the fragile environment are
    `-----000`, `1----001`, and `1----111`;
    and in the resilient environment are
    `-00--000`, `100--001`, and `100--111`" />
    </a>
    
    <figcaption><span><p>Figure 4. SPNE in harsh conditional return environments.
    From left to right, top to bottom, 
    the \(\boldsymbol{p}\)-groups in the fragile environment are
    <code class="language-plaintext highlighter-rouge">-----000</code>, <code class="language-plaintext highlighter-rouge">1----001</code>, and <code class="language-plaintext highlighter-rouge">1----111</code>;
    and in the resilient environment are
    <code class="language-plaintext highlighter-rouge">-00--000</code>, <code class="language-plaintext highlighter-rouge">100--001</code>, and <code class="language-plaintext highlighter-rouge">100--111</code></p>
</span></figcaption>
</figure>

<p>The conditions for return from the bad to good environment
can be classified as:</p>
<ol>
  <li>harsh conditional – both players must cooperate,</li>
  <li>conditional – at least one player must cooperate, or</li>
  <li>timeout – returns regardless of what players do.</li>
</ol>

<p>In all environments,
a variation that always defects in the bad environment in response to any
defection in the previous round is an unconditional SPNE.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2024/11/conditional.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2024/11/conditional.png" alt="Figure 5. SPNE in conditional return environments.
    From left to right, top to bottom, 
    the $$\boldsymbol{p}$$-groups in the fragile environment are
    `-00--000`, `100--001`, and `100--111`;
    and in the resilient environment are
    `-00----0` and `100----1`" />
    </a>
    
    <figcaption><span><p>Figure 5. SPNE in conditional return environments.
    From left to right, top to bottom, 
    the \(\boldsymbol{p}\)-groups in the fragile environment are
    <code class="language-plaintext highlighter-rouge">-00--000</code>, <code class="language-plaintext highlighter-rouge">100--001</code>, and <code class="language-plaintext highlighter-rouge">100--111</code>;
    and in the resilient environment are
    <code class="language-plaintext highlighter-rouge">-00----0</code> and <code class="language-plaintext highlighter-rouge">100----1</code></p>
</span></figcaption>
</figure>

<p>All other potential SPNE are conditional
and have (CC, good) as an attractor.
In the resilient environment,
they have in common a <code class="language-plaintext highlighter-rouge">100...</code> pattern,
which means that, if they are in the good environment,
they respond to defection by the other in the previous round 
with a defection.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2024/11/timeout.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2024/11/timeout.png" alt="Figure 6. SPNE in timeout environments.
    From left to right, top to bottom, 
    the $$\boldsymbol{p}$$-groups in the fragile environment are
    `-000-000`, `1000-001`, `100--111`, `1111-000` or `1001-00-`,
    and `1001-110`;
    and in the resilient environment are
    `-000---0`, `100----1`, and `1001---0`." />
    </a>
    
    <figcaption><span><p>Figure 6. SPNE in timeout environments.
    From left to right, top to bottom, 
    the \(\boldsymbol{p}\)-groups in the fragile environment are
    <code class="language-plaintext highlighter-rouge">-000-000</code>, <code class="language-plaintext highlighter-rouge">1000-001</code>, <code class="language-plaintext highlighter-rouge">100--111</code>, <code class="language-plaintext highlighter-rouge">1111-000</code> or <code class="language-plaintext highlighter-rouge">1001-00-</code>,
    and <code class="language-plaintext highlighter-rouge">1001-110</code>;
    and in the resilient environment are
    <code class="language-plaintext highlighter-rouge">-000---0</code>, <code class="language-plaintext highlighter-rouge">100----1</code>, and <code class="language-plaintext highlighter-rouge">1001---0</code>.</p>
</span></figcaption>
</figure>

<p>A strategy that defects in the bad environment
when the previous action was DD is only ever a potential SPNE in the timeout environment.
The strategy with the weakest necessary condition is such a strategy,
and it occurs in the resilient environment.</p>

<p>In the Github repository,
I also have results for \(\boldsymbol{q} = (1, -, 0; 1, 0, 1)\).
I call this a ‘strange return’ because the bad environment improves
when both players defect.
Perhaps unsurprisingly,
some of the strategies and conditions it finds are quite different
from those above,
and we can visualise those results similar to the ‘sensible’ environment above
and puzzle over what they mean.</p>

<h3 id="references">References</h3>

<p>Kleshnina, M., Hilbe, C., Šimsa, Š., Chatterjee, K. and Nowak, M.A., 2023. The effect of environmental information on evolution of cooperation in stochastic games. Nature Communications, 14(1), p.4153.</p>]]></content><author><name>nadiah_kristensen</name></author><category term="cooperation" /><summary type="html"><![CDATA[I recently read a paper by Kleshnina et al. (2023), The effect of environmental information on evolution of cooperation in stochastic games, which provided an opportunity to teach myself about how to analyse iterated games. In particular, the problem they investigated admits 64 possible scenarios with 256 possible strategies each, and I was interested in writing code that could automate the analysis. The solution I eventually landed on (Github repo) used a combination of SymPy, NetworkX, SageMath, the Z3 Theorem Prover, and PyEDA for Boolean minimisation, but I think my approach could be improved.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://nadiah.org/wp-content/uploads/2024/11/timeout_grim_wsls.png" /><media:content medium="image" url="https://nadiah.org/wp-content/uploads/2024/11/timeout_grim_wsls.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">A summary of Richard Joyce’s ‘The Evolution of Morality’</title><link href="https://nadiah.org/2024/10/15/joyce" rel="alternate" type="text/html" title="A summary of Richard Joyce’s ‘The Evolution of Morality’" /><published>2024-10-15T04:44:54+00:00</published><updated>2024-10-15T04:44:54+00:00</updated><id>https://nadiah.org/2024/10/15/joyce</id><content type="html" xml:base="https://nadiah.org/2024/10/15/joyce"><![CDATA[<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2024/10/Joyce06-Cover.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2024/10/Joyce06-Cover.png" alt="Metal." />
    </a>
    
    <figcaption><span><p>Metal.</p>
</span></figcaption>
</figure>

<p>To learn more about the evolution of cooperation from a philosopher’s perspective, I recently read Richard Joyce’s book <em>The Evolution of Morality</em>.
Joyce’s book makes the case for the evolutionary debunking argument,
which holds that moral beliefs are the product of evolutionary processes rather than tracking moral truths. 
While evolution has equipped us with the capacity for moral judgement, 
this doesn’t necessarily mean that our moral beliefs are true or justified. 
Instead, our moral sense evolved because it was useful for our ancestors’ survival and reproduction, 
regardless of whether moral facts actually exist.</p>

<p>Joyce begins by examining the evolutionary origins of helping behaviour, 
drawing on mechanisms familiar to evolutionary game theorists such as kin selection and mutualism. 
He proposes that emotions originally directed towards kin were co-opted for broader prosocial purposes. 
However, he argues this evolutionary account of helping behaviour falls well short of explaining morality in its full sense (Chapter 2). 
Even if humans had hypothetically evolved to behave in extremely altruistic ways, 
this alone would not constitute morality proper. The key distinction lies in the capacity for moral judgement. 
A creature could evolve with extreme inhibitions against behaviours we consider immoral, 
but without the concept of prohibitions and the ability to make moral judgements, it would not possess true morality.</p>

<p>This distinction leads Joyce to grapple with what moral judgement truly is. 
Some philosophers consider morality <em>conative</em>,
suggesting statements like “Stealing is wrong” are akin to exclaiming “STEALING!!” in an angry voice, 
expressing disapproval rather than a factual claim. 
However, Joyce contends that moral judgements also involve cognitive beliefs. 
To support this argument, he employs Moore’s paradox sentences
such as “Stealing is wrong, but I don’t believe it’s wrong”. 
The apparent contradiction arises because moral judgements also seem to express a cognitive belief.
(His later discussion of moral projectivism also makes a compelling argument.)</p>

<p>A second distinctive property of moral judgements is their <em>clout</em>. 
Compare “I disapprove of fox hunting” to “Fox hunting is wrong”;
the latter has much more ‘oomph’ than the former.
When someone declares that fox hunting is wrong, 
they’re not merely expressing a personal preference,
they’re making a claim that demands consideration from their audience regardless of the audience’s attitude towards the speaker or personal interests.</p>

<p>In Chapter 3, 
Joyce presents a contentious argument that non-human animals lack the capacity for moral judgement. 
He acknowledges that chimpanzees, for example, demonstrate complex social rules governing accepted and unaccepted behaviours (de Waal 1991). 
However, he argues these behaviours can be fully accounted for by aversions, inhibitions, and desires. 
He draws a distinction between these responses and the more sophisticated concept of <em>acceptable</em> and <em>unacceptable</em> behaviours, 
which involves evaluative concepts like transgression, prohibitions, and deserved punishment.</p>

<p>Joyce’s main argument for the absence of moral judgement in other animals hinges on the role of language, 
a stance he acknowledges is controversial among philosophers. 
He focuses on the concept of “thick evaluative terms” to illustrate his point. 
These are words like “kraut” that carry both a descriptive component (“German”) and an evaluative one (derogatory). 
Understanding such terms requires a “semantic ascent” — the ability to shift from discussing the subject to analysing the term itself
— which requires metalinguistic knowledge. 
Joyce argues that while other animals might grasp descriptive concepts like “German”, 
they lack the linguistic capacity for evaluative concepts like “kraut”. 
This linguistic limitation prevents other animals from making true moral judgements. 
Consequently, what we might perceive as “punishment” among other animals is more accurately described as a “negative response”.</p>

<p>He acknowledges some readers might be unpersuaded by his argument,
but a key question remains for us all: how does one move from having inhibitions to making judgements about prohibitions?</p>

<p>Joyce speculates that language may have evolved, in part, to facilitate the expression of evaluations, 
with gossip and indirect reciprocity playing pivotal roles. 
He argues that when we tell others about someone who cheated us, 
our goal isn’t to merely describe the event, but to criticise the action and potentially elicit a response from the listener.</p>

<p>Building on this, Joyce explores how many moral emotions are not only dependent on evaluative concepts but are also cognitively rich. 
He uses disgust as a prime example. While its biological root lies in food rejection, humans have elaborated this basic instinct into complex, 
almost superstitious beliefs. For example, people might refuse to wear a murderer’s jumper even after thorough laundering, 
revealing a concept of contamination that transcends physical reality. 
Joyce argues that while infants and non-human animals can experience distaste, 
they lack access to the full concept of disgust with its notions of invisible contaminants and realities distinct from appearance. 
Similarly, he examines guilt as another cognitively rich moral emotion. 
True guilt involves the concept that punishment is deserved rather than merely fearing or expecting it. 
While a dog might display guilt-like behaviour when acting submissively after wrongdoing, 
Joyce classifies this as proto-guilt, distinct from guilt proper.</p>

<p>In Chapter 4, 
Joyce examines the evolutionary benefits of self-directed moral judgement. 
He argues that moral conscience evolved as a more robust mechanism for scenarios where prudential judgement is too flexible and error-prone
(e.g., “I should eat this salad for my future health”). 
Moral conscience says an action <em>must</em> be performed, 
silencing further deliberation, and it provides a strong motivation akin to how orgasm incentivises sex.</p>

<p>A key evolutionary advantage of moral judgement is its ability to unite self-directed and other-directed evaluations, 
serving as a ‘common currency’ for collective decision-making. 
Self-directed judgements act as a motivational bulwark,
but they can also be publicly asserted, e.g., when staking a claim. 
The declaration ‘X is morally wrong’ demands avoidance from both the speaker and the audience.
But because moralised thinking is motivationally resolute,
it can also function as an interpersonal commitment. 
Emotions like indignant anger guarantee the pursuit of justice even at personal cost, 
which can be in one’s self-interest if it can be communicated to others.</p>

<p>Empirical evidence suggests that moral judgement is facilitated by emotions. 
Joyce argues for <strong>moral projectivism</strong>,
drawing an analogy with colour perception. 
Just as we project the qualia of ‘red’ onto objects, 
we project emotions like pity onto situations, 
perceiving them as objective features (a pitiful situation) rather than subjective responses.
This view can be contrasted with the traditional notion 
that moral judgements involve perceiving objective moral facts that exist independently in the world.
While not definitively proven, 
moral projectivism aligns with our understanding of sensory perceptions and is consistent with two key features of morality: 
the precedence of emotions in moral judgement (as demonstrated in experiments manipulating disgust), 
and the perception that moral attributes exist objectively,
which emerges in early childhood.</p>

<p>In contrast, Joyce finds it difficult to explain how moral judgements could be purely learnt.
He questions how abstract concepts like ‘moral transgression’ could be taught through punishment alone, 
or how children could infer the distinction between moral and conventional rules 
without wide exposure to the full range of cases necessary to make the inference
(e.g., an authority changing their mind about a moral transgression).
Instead,
he argues that,
while the content of the moral judgement may be learnt, moral judgement itself is innate.</p>

<p>In Chapter 5, Joyce examines attempts to vindicate morality, 
beginning with the distinction between global naturalism and moral naturalism. 
He illustrates this difference using an analogy: 
one could ‘naturalise witchcraft’ by providing sociological and anthropological accounts without asserting the existence of witches. 
Similarly, one could offer a scientifically respectable account of morality without claiming moral beliefs are true. 
<strong>Moral naturalism</strong>, however, goes further to assert that moral properties and relations genuinely exist.</p>

<p>The naturalistic fallacy, while not applicable to global naturalism, 
is often considered a significant challenge to moral naturalism. 
However, Joyce notes that the common understanding of the “naturalistic fallacy” — deriving an “ought” from an “is” — differs from its original meaning. 
He clarifies G.E. Moore’s initial argument, which is that ‘good’ is indefinable due to its simplicity. 
Moore contended that ‘good’, akin to ‘yellow’, lacks constituent parts and therefore resists decompositional definition 
(unlike, say, “horseness” = quadruped + gramnivorous + …). 
This argument is distinct from the issue of deriving ‘ought’ from ‘is’.</p>

<p>Addressing the derivation of ‘ought’ from ‘is’, Joyce argues that moral naturalism doesn’t necessarily violate this principle. 
He draws a parallel with how tables and chairs can be understood within the framework of physics: 
moral naturalists could potentially demonstrate that moral properties are either identical to or supervene upon natural properties. 
While this task is challenging, Joyce suggests it shouldn’t be dismissed outright as impossible.</p>

<p>Joyce contends that evolutionary vindications of morality often falter for two primary reasons. 
First, many advocates propose an instrumentalist form of justification, which opens the door to the possibility that moral beliefs are merely “useful fictions”. 
This approach fails in Joyce’s view because he adheres to partial cognitivism, maintaining that moral beliefs are genuine beliefs. 
Second, those who offer epistemic justifications tend to employ non-normative definitions of “ought” (e.g., equating “ought” with “will happen”)
that consequently fail to account for morality’s inherent clout.</p>

<p>Joyce then proceeds to examine several proposals in detail:</p>

<ol>
  <li>
    <p><strong>Richards (1986)</strong> argues that all justifications must eventually reach a stopping point, such as the <em>modus ponens</em> in logical arguments. He suggests that just as <em>modus ponens</em> derives its validity from the beliefs and practices of rational people, the transition from “is” to “ought” can be justified by examining the beliefs and practices of moral people.</p>

    <p>Joyce counters this by suggesting it would be more logical to look to rational, rather than moral, people. He questions whether <em>modus ponens</em> is truly justified solely by appealing to rational judgement and points out that widespread assent doesn’t equate to an inference rule.</p>

    <p>Richards also attempts to vindicate morality by proposing that evolution has adapted us to act for the community’s good, which he equates with morality.</p>

    <p>Joyce, while accepting this premise for argument’s sake, contends through counter-examples that community good isn’t synonymous with morality (e.g., Genghis Khan’s henchmen acted for their community’s good). Moreover, he argues that even if one accepts this equivalence, evolutionary considerations at best suggest that humans <em>will</em> act to benefit the community, not that they <em>ought</em> to.</p>
  </li>
  <li>
    <p><strong>Campbell (1996)</strong> contends that morality is justified because it improves people’s lives.</p>

    <p>Joyce critiques this, arguing that Campbell employs an incorrect notion of justification. He draws a parallel with religious belief: while it might improve lives, does that epistemically justify believing in gods? Joyce asserts that while this might provide instrumental justification, it falls short of the epistemic justification required for beliefs.</p>

    <p>Campbell further argues that epistemic justification is inappropriate for morality, claiming that moral beliefs are distinctive not in their subject matter, but in being essentially dispositions to think, feel, and act in certain ways.</p>

    <p>Joyce acknowledges that Campbell’s position is coherent, but only if morality is construed non-cognitively. He maintains that if moral beliefs are indeed <em>beliefs</em>, they must be subject to epistemic scrutiny.</p>
  </li>
  <li>
    <p><strong>Dennett (1995)</strong> posits that moral judgement plays a crucial practical role, and suggests that its efficacy might require avoiding excessive reflection on its functioning.</p>

    <p>Joyce notes that the first part of Dennett’s argument is an instrumental justification, similar to Campbell’s position. Regarding the second point, Joyce interprets Dennett as potentially advocating a form of <strong>moral fictionalism</strong>, to which he is sympathetic. This view holds that while moral judgements may not be true, and we can acknowledge this, it remains beneficial to treat them as true in our daily lives.</p>
  </li>
  <li>
    <p><strong>Casebeer (2003)</strong> proposes a neo-Aristotelian account of virtue, where human purpose is understood in Darwinian terms. While evolution provides a framework to discuss the purposes of specific organs like hands or eyes, it doesn’t assign an obvious function to humans as a whole, so one might bridge this gap by using a concept like ‘organism flourishing’.</p>

    <p>Joyce critiques this approach on several grounds. Firstly, he argues that a value system based on self-interest and harm avoidance doesn’t constitute a moral system in the traditional sense. He draws an analogy: we don’t praise a heart for pumping blood well, nor an assassin for excelling at their profession. Thus, evolution doesn’t provide a basis for moral ‘good’, even if such evaluations align with Aristotelian virtue ethics.</p>

    <p>Furthermore, Joyce invokes Mackie’s observation about the ‘queerness’ of moral properties. If moral properties exist, they imbue situations with demands for certain actions, regardless of an individual’s personal aims, and these demands derive authority from no human source, existing merely to be perceived by moral agents. Joyce argues that Casebeer’s approach doesn’t resolve this metaphysical issue. Just as a heart’s function to pump blood doesn’t imply a demand that it must do so, identifying a function for humans wouldn’t inherently create a moral imperative to fulfil it.</p>
  </li>
</ol>

<p>In the final chapter, 
Joyce addresses the evolutionary debunking of morality. 
He begins with a thought experiment: 
if you discovered you had been given a pill that made you believe Napoleon lost at Waterloo, 
wouldn’t that make you doubt this belief? Joyce argues that the discovery that our moral beliefs are products of evolution should prompt a similar doubt.</p>

<p>One might counter that natural selection tends to produce true beliefs. 
For example, if the belief that 1+1=2 were innate and evolved, its evolutionary origin wouldn’t invalidate it because its selective advantage stems from its truth. 
The same argument could be applied to the faculties of scientific inquiry that underpin the evolutionary debunking of morality itself. 
However, drawing on Harman (1977, 1986), 
Joyce contends that this reasoning doesn’t hold for moral beliefs. 
We can understand how moral beliefs evolved and benefited our ancestors without those beliefs necessarily being true. 
While “right” and “wrong” might denote something real about the world, 
they might do so in a manner akin to terms like “ghost” or “witch”.</p>

<p>If a non-moral genealogy can fully explain moral judgement, 
the onus falls on moral naturalists to demonstrate why moral facts aren’t redundant (Fig. 6.1). 
This would require a reductive account that explains how moral facts relate to natural ones.
Consider a hypothetical scenario: Jane sees children setting fire to a cat and judges it wrong. 
Can we explain Jane’s judgement without invoking the concepts of “cat”, “burning”, and “wrongness”? 
The concepts of “cat” and “burning” can be reduced to physics, so they’re implicitly present in that part of the causal explanation. 
However, if we can provide a naturalistic explanation for why the situation merely <em>seemed</em> wrong to Jane, 
we don’t need to posit the actual existence of wrongness to explain her judgement.</p>

<p>What constitutes an adequate reduction can be quite broad. 
While it’s challenging to move directly from physics and chemistry to concepts like “cat” and “burning”, 
we do have an account explaining how a burning cat is a physical and chemical entity. 
If a similar account can be provided for how moral facts are naturalistic entities, 
the critique would be satisfied (Fig. 6.2). 
However, to be credible, such an account would need to be as concrete and precise 
as a successful theory positing the existence of witches.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2024/10/Joyce06-Fig_6.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2024/10/Joyce06-Fig_6.png" alt="Figs. 6.1 and 6.2 contrast two situations for moral naturalism. In Fig. 6.1, the non-moral geneology explains moral judgements in an empirically verified way, which leaves the hypothesis of moral facts redundant.  However, if moral facts can be reduced to the non-moral facts used in the non-moral geneology (wriggly line 6.2), then moral facts can't be eliminated on the grounds of parsimony any more than cats are eliminated by physics and chemistry." />
    </a>
    
    <figcaption><span><p>Figs. 6.1 and 6.2 contrast two situations for moral naturalism. In Fig. 6.1, the non-moral geneology explains moral judgements in an empirically verified way, which leaves the hypothesis of moral facts redundant.  However, if moral facts can be reduced to the non-moral facts used in the non-moral geneology (wriggly line 6.2), then moral facts can’t be eliminated on the grounds of parsimony any more than cats are eliminated by physics and chemistry.</p>
</span></figcaption>
</figure>

<p>Joyce focuses on one key challenge facing moral naturalists: accounting for the inescapable practical authority that moral claims seem to possess. 
He chooses this example to illustrate a key point: 
if even a theory that equates moral facts with facts about reason can’t sufficiently explain this authority, what hope is there for theories that make this connection only indirectly?</p>

<p>The question becomes: how can naturalistic facts account for the clout of moral values? Essentially, we’re searching for a type of reason for action that can explain moral authority. Joyce examines two approaches:</p>

<ol>
  <li>
    <p><strong>Practical reasoning theory</strong> posits that an act is wrong if the agent has sufficient reasons not to do it. However, this theory falls short in explaining the inescapability of moral clout, which should have authority over people regardless of their personal interests.</p>
  </li>
  <li>
    <p>The <strong>self-conception strategy</strong> argues that a rational person must treat morality as authoritative to maintain a coherent self-conception (e.g., a sense of integrity and identity). Yet this approach also has limitations. The reasons for action still depend on our actual desires, and the intangibility of the supposed self-harm caused by immoral actions is problematic. While some proponents describe the consequences of immoral actions in catastrophic terms (e.g., losing self-value), this seems disproportionate for minor transgressions like nicking a pen, which the theory must also account for.</p>
  </li>
</ol>

<p>Some philosophers argue that clout is not a necessary feature of morality. 
However, Joyce contends that without this authoritative element, morality would function more like etiquette– a set of contingent rules rather than absolute imperatives. 
Even steadfast desires, he argues, are insufficient to capture the essence of moral judgements. 
Joyce questions what the notion “X is morally wrong” adds to an account based solely on likes and dislikes. 
He had previously argued that moral discourse is necessary because it acts as a bulwark in ways that non-moralised practical deliberation cannot. 
However, this function is unavailable to proponents of moral naturalism who reject the notion of moral clout. 
Moreover, admitting moral discourse is superfluous would be inconsistent with moral realism, 
creating a dilemma for those who seek to maintain moral naturalism without the element of inescapable authority.</p>

<p>As an alternative to metaphysical theories, Joyce considers epistemic approaches that might vindicate moral beliefs regardless of their ontological status:</p>
<ol>
  <li>
    <p><strong>Process reliabilism</strong> holds that beliefs are justified when produced by processes that reliably link belief with truth,
e.g., if natural selection favours organisms with at least approximately true innate beliefs.
However, this approach faces two problems.
First, the generality problem of specifying which is the relevant process (e.g., natural selection or specific cognitive mechanisms).
Second,
the deeper issue that natural selection may not always favour true beliefs, especially for intangible concepts like morality (cf., creation myths).
The established existence of such beliefs also forces us to conclude that we’re faced with an unreliable process.</p>
  </li>
  <li>
    <p><strong>Epistemic conservatism</strong> holds that existing beliefs are rational unless proven otherwise. 
However, Sinnott-Armstrong (2006) identifies five attributes that can cast doubt on beliefs
(a belief is partial, controversial, emotional, subject to illusion, and has unreliable/disreputable sources),
including being explicable by unreliable sources, which applies to moral beliefs.</p>
  </li>
  <li>
    <p><strong>Epistemic coherentism</strong> suggests <a href="https://nadiah.org/2024/04/15/coherence">beliefs are justified by fitting coherently with other beliefs</a>. 
However, when we include beliefs about the evolutionary origin of moral beliefs, 
which not only favour dropping a moral belief
but also gives a scientifically respectable explanation for why the error occurred in the first place,
then doubt is cast on the moral beliefs by coherentism’s own rules.</p>
  </li>
  <li>
    <p><strong>Moral intuitionism</strong>, a form of foundationalism, holds that some moral truths are self-evident. 
While this aligns with common intuitions about morality, items from Sinnott-Armstrong’s list (e.g., consider framing effects) again apply.
More to the point would be to ask why moral beliefs <em>seem</em> beyond question and seem not to need further justification, 
to which the evolutionary geneology provides a good answer.</p>
  </li>
</ol>

<h3 id="thoughts">Thoughts</h3>

<p>Joyce’s analysis has clarified for me a distinction in my research on the evolution of cooperation: 
while I’m seeking to explain cooperative behaviour, 
that falls short of addressing morality proper, which fundamentally involves judgement. 
The closest parallels are perhaps found in reputation models (indirect reciprocity), where rules emerge that align with our intuitions of “good” and “bad” behaviour (Ohtsuki and Iwasa 2004). 
However, even these models don’t fully capture the cognitive component and clout that Joyce identifies as essential to moral judgements.</p>

<p>Joyce’s emphasis on the role of clout suggests that before we can adequately model moral judgements, we need robust models of resolve and its failures. 
The concept of morality as an interpersonal commitment is particularly intriguing. 
It made me think of Sigmund et al.’s (2010) model of institutional punishment, 
though their model externalises commitment through pre-payment of taxes rather than internalising it. 
My own work on internal commitment in public goods games (in preparation, but similar to Newton’s (2017) threshold-PGG example) touches on related themes, 
but the commitment modelled there, being in the self-interest of collaborators, lacks the clout-bearing quality Joyce describes in true moral judgements.</p>

<p>I found it surprising that the primary challenges to moral naturalism centred on 
finding epistemic (rather than instrumental) justifications 
and employing a normative instead of descriptive version of “ought”. 
However, these challenges become more understandable in light of Joyce’s emphasis on cognitivism and clout. 
The concept of moral projectivism, with its clear analogy to qualia and sensory projection, struck me as particularly compelling.</p>

<h3>References</h3>

<p>Campbell, R., 1996. Can biology make ethics objective?. Biology and Philosophy, 11, pp.21-31.</p>

<p>Casebeer, W.D., 2003. Natural Ethical Facts: Evolution, Connectionism, and Moral Cognition. MIT Press.</p>

<p>de Waal, F.B., 1991. The chimpanzee’s sense of social regularity and its relation to the human sense of justice. American Behavioral Scientist, 34(3), pp.335-349.</p>

<p>Dennett, D.C., 1995. Darwin’s Dangerous Idea. Simon and Schuster.</p>

<p>Harman, G., 1977. The Nature of Morality: An Introduction to Ethics. Oxford University Press.</p>

<p>Harman, G., 1986. Moral explanations of natural facts—Can moral claims be tested against moral reality?. The Southern Journal of Philosophy, 24(S1), pp.57-68.</p>

<p>Joyce, R., 2006. The Evolution of Morality. MIT press.</p>

<p>Ohtsuki, H. and Iwasa, Y., 2004. How should we define goodness?—reputation dynamics in indirect reciprocity. Journal of theoretical biology, 231(1), pp.107-120.</p>

<p>Richards, R.J., 1986. A defense of evolutionary ethics. Biology and Philosophy, 1(3), pp.265-293.</p>

<p>Sigmund, K., De Silva, H., Traulsen, A. and Hauert, C., 2010. Social learning promotes institutions for governing the commons. Nature, 466(7308), pp.861-863.</p>

<p>Sinnott-Armstrong, W., 2006. Moral intuitionism meets empirical psychology. In Metaethics after Moore, ed. T. Horgan and M. Timmons. Oxford University Press.</p>]]></content><author><name>nadiah_kristensen</name></author><category term="cooperation" /><summary type="html"><![CDATA[Metal.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://nadiah.org/wp-content/uploads/2024/10/Joyce06-Cover.png" /><media:content medium="image" url="https://nadiah.org/wp-content/uploads/2024/10/Joyce06-Cover.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Coherence-based reasoning and the downplaying of contrary evidence</title><link href="https://nadiah.org/2024/04/15/coherence" rel="alternate" type="text/html" title="Coherence-based reasoning and the downplaying of contrary evidence" /><published>2024-04-15T04:44:54+00:00</published><updated>2024-04-15T04:44:54+00:00</updated><id>https://nadiah.org/2024/04/15/coherence</id><content type="html" xml:base="https://nadiah.org/2024/04/15/coherence"><![CDATA[<p>Human reasoning often defies the ideal of normative inference, where conclusions should logically follow from evidence. 
Instead, we frequently engage in ‘backward reasoning’, starting with preferred conclusions and selectively seeking supporting evidence while discounting contradictory information. 
This tendency reflects a broader cognitive preference for beliefs that align with our existing worldview.</p>

<p>This inclination towards coherent beliefs contrasts sharply with foundationalist approaches to knowledge, 
which advocate building beliefs from self-evident truths (e.g., Descartes’ “I think; therefore, I am”). 
Our belief systems instead resemble intricate webs, characterised by complex interdependencies rather than clear hierarchies.
Coherence-based reasoning is a cognitive model that attempts to capture these realities of human thought,
where our beliefs are formed and held within this interconnected framework. 
This model not only describes how we form new beliefs
but also how we maintain and adjust our existing belief systems in the face of new information.</p>

<p>Experimental evidence supports the key hypotheses of coherence-based reasoning, 
showing that incipient conclusions can change how evidence is weighted. 
For example, in an experiment where participants were asked to judge a legal case, 
their evaluation of the evidence shifted to align with their eventual conclusions (e.g., Fig. 1).
This ‘coherence shift’ phenomenon underscores the bidirectional nature of human reasoning processes.</p>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2024/04/Simon04-Fig3.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2024/04/Simon04-Fig3.png" alt="Figure 1. Coherence shift in legal reasoning. Data from Simon (2004) shows how participants’ categorisation of the Internet (as more similar to a newspaper or a telephone) shifted to align with their verdict in a libel case. This categorisation was crucial for determining the applicability of libel law, demonstrating how conclusions can influence the interpretation of evidence." />
    </a>
    
    <figcaption><span><p>Figure 1. Coherence shift in legal reasoning. Data from Simon (2004) shows how participants’ categorisation of the Internet (as more similar to a newspaper or a telephone) shifted to align with their verdict in a libel case. This categorisation was crucial for determining the applicability of libel law, demonstrating how conclusions can influence the interpretation of evidence.</p>
</span></figcaption>
</figure>

<p>Coherence-based reasoning, a Gestaltian cognitive-consistency model of human reasoning (reviewed in Simon and Read (2023)), 
is founded on the observation that we tend to accept propositions that align with our existing beliefs and reject those that contradict them. 
This theory, as formalised by Thagard (1989) in the ECHO (Explanatory Coherence by Harmony Optimisation) model, 
offers a compelling alternative to unidirectional theories of reasoning such as Bayesian inference. 
Proponents of coherence-based reasoning, like Simon et al. (2004), argue that the theory’s ability to capture ‘coherence shifts’ 
– such as those observed in the legal judgement experiment described earlier — 
makes it a more accurate representation of human cognitive processes (see also: Simon (2004); Glöckner and Betsch (2008)).</p>

<p>The ECHO model, proposed by Thagard (1989), formalises coherence-based reasoning using a connectionist framework. 
This model can be simulated using neural network techniques (e.g., Fig. 2) and consists of the following elements:</p>
<ol>
  <li>Nodes: Represent individual pieces of evidence and hypotheses.</li>
  <li>Weighted edges: Signify relationships between nodes.
    <ol>
      <li>Positive weights: Indicate explanatory links between hypotheses and evidence, co-hypotheses, or analogical connections.</li>
      <li>(b) Negative weights: Represent contradictions between hypotheses.</li>
    </ol>
  </li>
</ol>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2024/04/Thagard89_Fig_3_to_8.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2024/04/Thagard89_Fig_3_to_8.png" alt="Figure 2. Examples of coherence networks from Thagard (1989), each illustrating a principle of explanatory coherence." />
    </a>
    
    <figcaption><span><p>Figure 2. Examples of coherence networks from Thagard (1989), each illustrating a principle of explanatory coherence.</p>
</span></figcaption>
</figure>

<p>This network structure allows the model to simulate the complex interplay between different pieces of information and beliefs, 
mirroring the web-like nature of human cognition. 
By observing how activation spreads through a network, 
we can gain insights into how people might integrate new information, resolve contradictions, and ultimately form coherent belief systems.</p>

<h3>Example</h3>

<p>Consider the network structure illustrated in Fig. 3a, adapted from Fig. 8 of Thagard (1989). 
This network represents a scenario with five pieces of evidence (E1 to E5) and four hypotheses (H1 to H4). 
The relationships between these elements are as follows:</p>
<ol>
  <li>Hypothesis H1 exclusively explains evidence E1 and also accounts for E2, E3, and E4.</li>
  <li>Evidence E5 is not explained by H1 but can be explained by hypotheses H2, H3, and H4.</li>
  <li>Hypotheses H2, H3, and H4 each explain only one additional piece of evidence besides E5.</li>
  <li>Hypotheses H2, H3, and H4 are mutually exclusive with H1, indicated by dashed lines.</li>
  <li>A ’special’ node represents data priority, connected to all evidence nodes.</li>
</ol>

<figure style="max-width: 700px; margin: auto; padding-bottom: 20px;">
    
    <a href="/wp-content/uploads/2024/04/downplay_evidence.png">
        <img style="max-height: auto; max-width: 100%;" src="/wp-content/uploads/2024/04/downplay_evidence.png" alt="Figure 3. Example from Fig. 8 of Thagard (1989): (a) network structure, and (b) activation of selected nodes over time." />
    </a>
    
    <figcaption><span><p>Figure 3. Example from Fig. 8 of Thagard (1989): (a) network structure, and (b) activation of selected nodes over time.</p>
</span></figcaption>
</figure>

<p>To simulate this model, activation propagates from the data to the evidence nodes, 
which in turn activate their connected hypothesis nodes. 
The nodes then influence each other based on their positive (solid lines) or negative (dashed lines) relationships. 
This process continues until the system reaches a steady state, 
representing the final decision regarding the believability of the hypotheses and evidences.</p>

<p>The simulation requires setting initial weights for the links between nodes. 
Following Thagard (1989), I used the default weightings:</p>
<ul>
  <li>\(w_{ij} = 0.05\) for excitatory links, and</li>
  <li>\(w_{ij} = -0.2\) for inhibitory links.</li>
</ul>

<p>These weightings may by modified to penalise the complexity of an explanation 
and to adjust the weighting of analogical links (no analogical links in this example).</p>

<p>The model is simulated as follows.</p>
<ol>
  <li>Initialise the `data priority’ node’s activation level \(a_0 = 1\) and all other nodes at some small initial value (default \(a_i = 0.001\)).</li>
  <li>For each timestep, update all nodes \(j &gt; 0\) while keeping data-priority node constant at \(a_0 = 1\):
    <ol>
      <li>
        <p>For each evidence and hypothesis node \(j\), calculate the net input to that node from all other nodes \(i\) connected to it</p>

\[\text{net}_j = \sum_i w_{ij} a_i
     \label{E:net_j}\]
      </li>
      <li>
        <p>For each evidence and hypothesis node \(j\), update the node’s activation level \(a_j\) according to</p>

\[a_j(t+1) = a_j(t) (1 - \theta) +
         \begin{cases}
             \text{net}_j (\text{max} - a_j(t)) &amp; \text{if } \text{net}_j &gt; 0, \\
             \text{net}_j (a_j(t) - \text{min}) &amp; \text{otherwise.}
         \end{cases}
     \label{E:a_j_update}\]

        <p>where max is the maximum node activation (default \(\text{max} = 1\)),
 min is the minimum node activation (default \(\text{min} = -1\)),
 and \(\theta\) is an activation decay parameter (default \(\theta = 0.05\)).</p>
      </li>
    </ol>
  </li>
  <li>Repeat Step 2 until the maximum absolute change in activation level of any node falls below a set tolerance (default \(\text{tol} = 0.001\)).</li>
</ol>

<p>Fig. 3b illustrates how the activation levels of selected nodes in our example change over time. 
For example, the activation level of hypothesis H1 increases over time while all others H2-H4 decrease below zero. 
This represents the process of arriving at the conclusion that H1 is true and the other hypotheses false.</p>

<p>Notably, the example also illustrates the potential for evidence suppression. 
Although E5 is an established piece of evidence, its activation decreases over time.
This occurs because E5 only supports the less favoured hypotheses H2-H4, 
which are ultimately rejected by the model.</p>

<p>Thagard (1989) explains the rationale between this counter-intuitive behaviour:</p>
<blockquote>
  <p>Principle 4 [of his 7 principles of explanatory coherence] asserts that data get priority by virtue of their independent coherence. But it should nevertheless be possible for a data unit to be deactivated.  We see this both in the everyday practice of experimenters, in which it is often necessary to discard some the data because they are deemed unreliable (Hedges 1987), and in the history of science where evidence for a discarded theory sometimes falls into neglect (Lauden 1976).</p>
</blockquote>

<h3>Final thoughts</h3>

<p>While we often view evidence suppression as a cognitive flaw, the model and example above suggests it may be an emergent property of an otherwise adaptive mechanism. 
Our ability to discount or deactivate certain pieces of information, like E5 in the example above, enables us to form coherent, actionable beliefs in a world of noisy,
often contradictory data. 
This process isn’t about wilfully ignoring evidence, but rather about contextualising it within a broader explanatory framework.</p>

<p>While this coherence-seeking tendency can be advantageous in many real-world scenarios, 
it may also underlie problematic cognitive biases such as confirmation bias or belief perseverance. 
Understanding this dual nature of coherence-based reasoning — its adaptive value and potential pitfalls — 
offers a rather nuanced view of human cognition, and may lead to more sophisticated models of reasoning and decision-making.</p>

<h3>References</h3>

<p>Glöckner, A. and Betsch, T. (2008). Modeling option and strategy choices with connectionist networks: Towards an integrative model of automatic and deliberate decision making, Judgment and Decision making 3(3): 215–228.</p>

<p>Simon, D. (2004). A third view of the black box: Cognitive coherence in legal decision making, University of Chicago Law Review 71: 511.</p>

<p>Simon, D. and Read, S. J. (2023). Toward a general framework of biased reasoning: Coherence-based reasoning, Perspectives on Psychological Science p. 17456916231204579.</p>

<p>Simon, D., Snow, C. J. and Read, S. J. (2004). The redux of cognitive consistency theories: evidence judgments by constraint satisfaction, Journal of Personality and Social Psychology 86(6): 814.</p>

<p>Thagard, P. (1989). Explanatory coherence, Behavioral and Brain Sciences 12(3): 435–467.</p>]]></content><author><name>nadiah_kristensen</name></author><category term="cooperation" /><summary type="html"><![CDATA[Human reasoning often defies the ideal of normative inference, where conclusions should logically follow from evidence. Instead, we frequently engage in ‘backward reasoning’, starting with preferred conclusions and selectively seeking supporting evidence while discounting contradictory information. This tendency reflects a broader cognitive preference for beliefs that align with our existing worldview.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://nadiah.org/wp-content/uploads/2024/04/Thagard89_Fig_3.png" /><media:content medium="image" url="https://nadiah.org/wp-content/uploads/2024/04/Thagard89_Fig_3.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>