Skip to content

Conversation

@colinfrisch
Copy link
Collaborator

Virus-Antibody Model

This model is a simulation of immune reaction declined as a confrontation between antibody agents and virus agents. The global idea is to model how the imune system can struggle against new virus but is able to adapt over time and beat a same virus if it comes back. The results are quite interesting as the simulation can go both ways (virus win or antibodies win) with a little tweak in the base parameters (check the readme for some examples).

What it showcases

  • Usage of memory in agents : divided into a short term memory using a deque to easily add and remove memories in case of a new virus encounter, and a long term memory (here a simple list)
  • Agent knowledge sharing : the antibodies are able to share short term memory)
  • Usage of weak referencing to avoid coding errors (antibodies can store viruses in a self.target attribute)
  • Emergence of completely different outcomes with only small changes in parameters

What it adds to mesa-examples

  • Will be the first continuous‑space user example
  • Weak‑referencing and dual memory systems and share knowledge (cf. what it showcases)
  • Theme is original and quite different from what already exists in examples : shows the diversity of fields in which mesa can be useful

How It Works

  1. Initialization: The model initializes a population of viruses and antibodies in a continuous 2D space.
  2. Agent Behavior:
    • Antibodies move randomly until they detect a virus within their sight range (becomes purple), than pursue the virus.
    • Antibodies pass on all the virus DNA in their short term memory to the nearest antibodies (cf. example)
    • Viruses move randomly and can duplicate or mutate.
  3. Engagement (antibody vs virus): When an antibody encounters a virus:
    • If the antibody has the virus's DNA in its memory, it destroys the virus.
    • Otherwise, the virus may defeat the antibody, causing it to lose health or become inactive temporarily.
  4. Duplication: Antibodies and viruses can duplicate according to their duplication rate.
pattern

@coderabbitai
Copy link

coderabbitai bot commented Apr 18, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request introduces a simulation that models the interaction of viruses and antibodies in continuous space using the Mesa framework. The PR implements the core model logic, defines agent behaviors with dual memory systems and weak referencing, and sets up visualization components with an accompanying README for usage instructions.

  • Implements the core Virus/Antibody simulation with model parameters and agent scheduling.
  • Adds visualization components and parameter interfaces in the application module.
  • Documents the model details, simulation outcomes, and usage instructions in the README.

Reviewed Changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 4 comments.

File Description
examples/virus_antibody/model.py Implements the primary model setup and simulation step behavior.
examples/virus_antibody/app.py Provides the visualization interface and model parameter configurations.
examples/virus_antibody/agents.py Defines antibody and virus agent behaviors with memory, duplication, and engagement logic.
examples/virus_antibody/README.md Provides documentation covering model overview, usage, and simulation examples.
Files not reviewed (1)
  • examples/virus_antibody/requirements.txt: Language not supported

)

# Create and place the Antibody agents
antibodies_positions = self.rng.random(
Copy link

Copilot AI Apr 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent use of random number generators (self.rng vs self.random) may lead to unpredictable behavior; consider unifying these to a single random instance.

Copilot uses AI. Check for mistakes.

# Movement & state
self.position = initial_position
self.speed = 1.5
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to make speed and health kwargs on the init? In my view it is good practice to make all attributes explicitly controllable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made the choice of limiting the parameter to these ones because otherwise it's a lot for the user in my opinion. If I add all of them, it would mean more than 10 parameters to be decided by the user. Do you think I should still add all of the other parameters ? Or are there one or two more that would be good to add and still leave the others hidden (like ko_timeout, sight_range, and a few non-crucial parameters) ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. One option is to turn the constants that are the same for all agents of a given class into class level attributes. This still makes it easy to change them without having to add them all to the signature of the __init__.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea ! It does allow to delete quite a few lines of code in model.py also.


# Chase a valid virus target
else:
if getattr(target, "space", None) is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, this should not be needed if everything else is implemented correctly.

Copy link
Member

@quaquel quaquel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made a first pass. I like the idea of the model, but there is ample room for improvements. If you have questions, don't hesitate to ask.

self.lt_memory.append(dna)
self.ko_steps_left = self.ko_timeout
# mark KO state by weak-ref back to self
self.target = weakref.ref(self)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont understand what is going on here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When an antibody looses a confrontation with a virus (because the virus DNA is not in it's memory yet), the antibody is ko for a few steps. It can't move during this time and I symbolise this by setting the agent's target to itself.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, clear.

This might be implemented using event scheduling, which would result in a modest speedup of runtime.

@colinfrisch
Copy link
Collaborator Author

Thanks a lot @quaquel for this first review ! I'm going to see what I can change asap

@quaquel
Copy link
Member

quaquel commented Apr 19, 2025

Thanks a lot @quaquel for this first review ! I'm going to see what I can change asap

I would start with the two sets in the the init of your model. Next, I would remove the if checks you have to see if an agent is still active in the model one at a time (or do one first to confirm the behavior is still correct). Again, don't hesitate to ask if you run into any issues.

@colinfrisch
Copy link
Collaborator Author

colinfrisch commented Apr 20, 2025

I would start with the two sets in the the init of your model. Next, I would remove the if checks you have to see if an agent is still active in the model one at a time (or do one first to confirm the behavior is still correct). Again, don't hesitate to ask if you run into any issues.

@quaquel I removed the checks and did the other fixes that you suggested. I still have one error that I don't understand, and it seems to be still linked to referencing. For certain configurations of parameters, the model runs for a while (even hundreds of steps) before making the error error in step: 'NoneType' object has no attribute 'agent_positions'. Even after debugging, I can't find the origin of it... would you have any idea to why it's happening ?

You can reproduce this error by keeping the base parameters and setting duplication rate to max in the sliders.

Screenshot 2025-04-20 at 18 27 20

Full traceback :

  File "/Users/colinfrisch/Desktop/mesa/mesa/visualization/solara_viz.py", line 275, in step
    do_step()
    ~~~~~~~^^
  File "/Users/colinfrisch/Desktop/mesa/mesa/mesa_logging.py", line 132, in wrapper
    res = func(*args, **kwargs)
  File "/Users/colinfrisch/Desktop/mesa/mesa/visualization/solara_viz.py", line 309, in do_step
    model.value.step()
    ~~~~~~~~~~~~~~~~^^
  File "/Users/colinfrisch/Desktop/mesa/mesa/model.py", line 122, in _wrapped_step
    self._user_step(*args, **kwargs)
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/Users/colinfrisch/Desktop/mesa-examples/examples/virus_antibody/model.py", line 131, in step
    self.agents.shuffle_do("step")
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
  File "/Users/colinfrisch/Desktop/mesa/mesa/agent.py", line 345, in shuffle_do
    getattr(agent, method)(*args, **kwargs)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/Users/colinfrisch/Desktop/mesa-examples/examples/virus_antibody/agents.py", line 185, in step
    self.move()
    ~~~~~~~~~^^
  File "/Users/colinfrisch/Desktop/mesa-examples/examples/virus_antibody/agents.py", line 221, in move
    self.position += self.direction * self.speed
    ^^^^^^^^^^^^^
  File "/Users/colinfrisch/Desktop/mesa/mesa/experimental/continuous_space/continuous_space_agents.py", line 34, in position
    return self.space.agent_positions[self.space._agent_to_index[self]]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'agent_positions'

@quaquel
Copy link
Member

quaquel commented Apr 20, 2025

I'll try to take a closer look asap. It indeed seems that step is called on an agent that has already been removed.

@quaquel quaquel closed this Apr 20, 2025
@quaquel quaquel reopened this Apr 20, 2025
return dna

def move(self):
# Random walk
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both agents have a random walk component. Might it be possible to implement this the same way for both and move it to a new super agent class from which both current agents derive?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be a good idea ! At first I thought it could also be used for the duplicate() method. However, there are a lot of parameters to take into account and the code is very short inside, so I don't think that it would really help to make it derive from a parent class.

So I the end, there is just a few lines for the random movement. Isn't a whole parent class a bit overkill ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the choice is between repeated code or a parent class, I prefer the parent class. Of course, in python you could also move the relevant code into a helper function (not really appropriate for random walks).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, will do, thank you for the advice !
On another note, did you have time to check out why step was being called on a removed agent ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Monday was a banking holiday so I had family obligations. I'll try to get to it today, but no promises unfortunately.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, nothing urgent ! Only if you have time :)

@wang-boyu
Copy link
Member

I'm not very familiar with how weakref works in AgentSet, so I could be wrong about this. If an agent's hard references are all removed, does its object guaranteed to be destroyed? If it's not, then there might be problems from here (and other similar places in AgentSet.do() and AgentSet.shuffle_do()):

https://github.com/projectmesa/mesa/blob/94c56a359b967da7b9d23e7df7bdc3ef446ab158/mesa/agent.py#L327

Suppose there are 50 agents, and Agent # 1 removed Agent # 7 in its step() function. Imagine after this, Agent # 7's object is not destroyed immediately, then AgentSet.do() or AgentSet.shuffle_do() will still try to run Agent # 7's step() function.

This may also happen if users unintentionally use hard references (e.g., self.target = another_agent instead of using a weakref that is shown in this PR). I understand projectmesa/mesa#2763 may fix this. However, there are just too many ways that hard references can happen, and I don't think it's possible to make sure that all references to Agent are weak refs. Examples include self.some_dict[target] = another_agent, self.some_list.append(another_agent), or setting attributes in a non-Agent class.

Overall, I think we may need a better way to do null checks in AgentSet.do() and AgentSet.shuffle_do().

In addition, there might be another problem in AgentSet.do() when we do

for agentref in self._agents.keyrefs():
    ...

As agents are removed (deregistered), keyrefs() are updated. It is not really a good design to update iterable while looping through it. In contrast, AgentSet.shuffle_do() seems good by iterating a copy of keyrefs():

weakrefs = list(self._agents.keyrefs())
...
for ref in weakrefs:
    ...

@quaquel
Copy link
Member

quaquel commented Apr 30, 2025

I'm not very familiar with how weakref works in AgentSet, so I could be wrong about this. If an agent's hard references are all removed, does its object guaranteed to be destroyed?

This is indeed the central question. I just need time to dive into this and do some research. The random nature of the issue suggests that there is no hard guarantee that an agent is removed immediately upon its reference counter hitting 0. But I want to confirm this and read up on the garbage collection in Python (which is responsible for clearing up objects whose reference counter has hit 0).

@colinfrisch
Copy link
Collaborator Author

I'm currently doing some experimentations on automating weak referencing, and it seems to come most of the time with excess overhead. So I agree with boyu that it might be worth focusing rather on null checks.

In the meantime, should I remove the problem by adding checks so it's still functional ?

@EwoutH
Copy link
Member

EwoutH commented Oct 29, 2025

This seems like a cool example model. Is anything blocking from getting it merged?

@colinfrisch
Copy link
Collaborator Author

It's been a long time since I've last worked on it. I'll try to make sure that everything is okay with it during the new few days so that we merge something truly functional. I'll get back to you asap.

@EwoutH
Copy link
Member

EwoutH commented Oct 29, 2025

Awesome, if you want anything reviewed be sure to let me know!

@colinfrisch
Copy link
Collaborator Author

Hey @EwoutH sorry it's been a while, but I've reviewed the code and it looks good to merge if there is no problem for you.

EwoutH and others added 2 commits November 28, 2025 17:15
Change absolute import to relative import for the agents module in model.py and remove unnecessary sys.path manipulation from both model.py and agents.py. This aligns with the import pattern used by other example models and fixes the ModuleNotFoundError that occurred during test collection.
@EwoutH
Copy link
Member

EwoutH commented Nov 28, 2025

Thanks! Did some rebasing and fixed the imports, CI passes now. Merging, further improvements can be made later.

@EwoutH EwoutH changed the title virus antibody model Add Virus-Antibody model Nov 28, 2025
@EwoutH EwoutH merged commit 047b407 into projectmesa:main Nov 28, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants