-
-
Notifications
You must be signed in to change notification settings - Fork 13
Sugarscape g1mt #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sujalgawas
wants to merge
8
commits into
projectmesa:main
Choose a base branch
from
sujalgawas:sugarscrap_g1mt
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+636
−0
Open
Sugarscape g1mt #35
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
25d4f32
inital commit model.py and agent.py
sujalgawas a07e90c
runs with errors
sujalgawas c200a52
runs with errors
sujalgawas 51f912a
fixed harvesting logic
sujalgawas f1e7469
final tests with gemini-2.5-flash-lite
sujalgawas 8eb0fe1
Readme.md file added
sujalgawas 366323a
readme.md
sujalgawas 86cb20d
Fix harvesting logic in tools.py
sujalgawas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| # Sugarscape Constant Growback Model with Traders | ||
|
|
||
| ## Summary | ||
|
|
||
| This model is based on Epstein & Axtell's classic "Sugarscape" simulation from Growing Artificial Societies (1996), specifically the G1MT (Growback 1, Metabolism, Trade) variation. Trader agents wander a grid populated with two unevenly distributed resources: Sugar and Spice. Agents are endowed with individual metabolic rates for each resource and a vision range; there are also Resource agents, which represent the landscape and regenerate food over time. | ||
|
|
||
| The model generates emergent economic dynamics through decentralized interactions. Traders must constantly harvest resources to satisfy their metabolic needs; if they run out of either sugar or spice, they starve. Crucially, agents can trade with neighbors. Decisions are governed by the Marginal Rate of Substitution (MRS); agents rich in sugar but poor in spice will trade sugar to acquire spice, and vice versa. Over time, this decentralized trading allows for the emergence of a price equilibrium and wealth distribution patterns. | ||
|
|
||
| This model is implemented using Mesa-LLM, unlike the original deterministic versions. All Trader agents use Large Language Models to "think" about their survival. They observe their internal inventory and MRS, then autonomously decide to use tools to move to high-value resource tiles or propose trades to neighbors to ensure their continued existence. | ||
|
|
||
| ## Technical Details | ||
|
|
||
| ### Agents | ||
|
|
||
| - `Trader (LLMAgent):` The primary actor equipped with STLTMemory and ReActReasoning. | ||
|
|
||
| Internal State: Dynamically updates a context string with current inventory (Sugar, Spice) and hunger warnings to guide the LLM. | ||
|
|
||
| Metabolism: Consumes a fixed amount of resources per step. Zero inventory results in agent removal (death). | ||
|
|
||
| MRS Calculation: Computes the Marginal Rate of Substitution (MRS) using the Cobb-Douglas formula to value Sugar vs. Spice relative to biological needs. | ||
|
|
||
| - `Resource (CellAgent):` A passive environmental agent that acts as a container for resources. It regenerates its current_amount by 1 unit per step up to a max_capacity. | ||
|
|
||
| ### Tools | ||
|
|
||
| - `move_to_best_resource:` | ||
|
|
||
| Function: Scans the local grid within the agent's vision radius. | ||
|
|
||
| Action: Identifies the cell with the highest current_amount of resources, moves the agent there, and automatically harvests the full amount into the agent's inventory. | ||
|
|
||
| - `propose_trade:` | ||
|
|
||
| Function: Targets a specific neighbor by unique_id. | ||
|
|
||
| Logic: Executes a trade only if the partner's MRS is higher than the proposer's (indicating the partner values Sugar more highly). This ensures trades are mathematically rational and mutually beneficial. | ||
|
|
||
|
|
||
| ### Movement Rule (Rule M) | ||
|
|
||
| A Trader agent moves to a new location and harvests resources if the following logic, executed by the *move_to_best_resource tool*, is satisfied: | ||
|
|
||
| Scan: The agent inspects all cells within its *vision range* (von Neumann neighborhood). | ||
|
|
||
| Identify: It identifies the cell containing a *Resource* agent with the highest *current_amount* of sugar/spice. | ||
|
|
||
| Harvest: The agent moves to that cell, sets the resource's amount to 0, and adds the harvested amount to its own inventory. | ||
|
|
||
| ### Trade Rule (Rule T) | ||
|
|
||
| Agents determine whether to trade based on their Marginal Rate of Substitution (MRS). A trade is proposed via the propose_trade tool and accepted if it is mutually beneficial: | ||
|
|
||
| ``` | ||
| Trade occurs if: Partner_MRS > Agent_MRS | ||
| ``` | ||
|
|
||
| Where the MRS is calculated using the agent's inventory and metabolism: | ||
|
|
||
| ``` | ||
| MRS = (spice_inventory / spice_metabolism) / (sugar_inventory / sugar_metabolism) | ||
| ``` | ||
|
|
||
| In this implementation: | ||
|
|
||
| - `Agent (Proposer):` Gives Sugar, Receives Spice. | ||
|
|
||
| - `Partner (Receiver):` Receives Sugar, Gives Spice. | ||
|
|
||
| - This flow ensures resources move from agents who value them less to agents who value them more. | ||
|
|
||
| ### Resource Behavior | ||
|
|
||
| Resource Agents represent the landscape. They are passive agents that regenerate wealth over time: | ||
|
|
||
| - `Growback:` At every step, a Resource agent increases its current_amount by growback (default: 1). | ||
|
|
||
| - `Capacity:` This growth is capped at the agent's max_capacity. | ||
|
|
||
| ### LLM-Powered Agents | ||
|
|
||
| Both Traders and the simulation logic are driven by LLM-powered agents, meaning: | ||
|
|
||
| - Their actions (e.g., `move_to_best_resource`, `propose_trade`) are determined by a ReAct reasoning module. | ||
|
|
||
| - This module takes as input: | ||
|
|
||
| The agent’s internal state (current inventory, metabolic warnings, and calculated MRS). | ||
|
|
||
| Local observations of the grid. | ||
|
|
||
| - A set of available tools defined in `tools.py`. | ||
|
|
||
|
|
||
|
|
||
| ## How to Run | ||
|
|
||
| If you have cloned the repo into your local machine, ensure you run the following command from the root of the library: ``pip install -e . ``. Then, you will need an api key of an LLM-provider of your choice. (This model in particular makes a large amount of calls per minute and we therefore recommend getting a paid version of an api-key that can offer high rate-limits). Once you have obtained the api-key follow the below steps to set it up for this model. | ||
| 1) Ensure the dotenv package is installed. If not, run ``pip install python-dotenv``. | ||
| 2) In the root folder of the project, create a file named .env. | ||
| 3) If you are using openAI's api key, add the following command in the .env file: ``OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx``. If you have the paid version of Gemini, use this line instead: ``GEMINI_API_KEY=your-gemini-api-key-here``(the free ones tend to not work with this model). | ||
| 4) Change the ``api_key`` specification in app.py according to the provider you have chosen. | ||
| 5) Similarly change the ``llm_model`` attribute as well in app.py to the name of a model you have access to. Ensure it is in the form of {provider}/{model_name}. For e.g. ``openai/gpt-4o-mini``. | ||
|
|
||
| Once you have set up the api-key in your system, run the following command from this directory: | ||
|
|
||
| ``` | ||
| $ solara run app.py | ||
| ``` | ||
|
|
||
|
|
||
| ## Files | ||
|
|
||
| * ``model.py``: Core model code. | ||
| * ``agents.py``: Agent classes. | ||
| * ``app.py``: Sets up the interactive visualization. | ||
| * ``tools.py``: Tools for the llm-agents to use. | ||
|
|
||
|
|
||
| ## Further Reading | ||
|
|
||
| [Growing Artificial Societies](https://mitpress.mit.edu/9780262550253/growing-artificial-societies/) | ||
| [Complexity Explorer Sugarscape with Traders Tutorial](https://www.complexityexplorer.org/courses/172-agent-based-models-with-python-an-introduction-to-mesa#gsc.tab=0) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| import examples.sugarscrap_g1mt.tools # noqa: F401, to register tools |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,162 @@ | ||
| from enum import Enum | ||
|
|
||
| import mesa | ||
|
|
||
| from mesa_llm.llm_agent import LLMAgent | ||
| from mesa_llm.memory.st_lt_memory import STLTMemory | ||
| from mesa_llm.tools.tool_manager import ToolManager | ||
|
|
||
| trader_tool_manager = ToolManager() | ||
| resource_tool_manager = ToolManager() | ||
|
|
||
|
|
||
| class TraderState(Enum): | ||
| Total_Spice = 4 | ||
| Total_Sugar = 6 | ||
| Total_Count = 10 | ||
|
|
||
|
|
||
| class Trader(LLMAgent, mesa.discrete_space.CellAgent): | ||
| def __init__( | ||
| self, | ||
| model, | ||
| reasoning, | ||
| llm_model, | ||
| system_prompt, | ||
| vision, | ||
| internal_state, | ||
| step_prompt, | ||
| sugar=0, | ||
| spice=0, | ||
| metabolism_sugar=1, | ||
| metabolism_spice=1, | ||
| ): | ||
| super().__init__( | ||
| model=model, | ||
| reasoning=reasoning, | ||
| llm_model=llm_model, | ||
| system_prompt=system_prompt, | ||
| vision=vision, | ||
| internal_state=internal_state, | ||
| step_prompt=step_prompt, | ||
| ) | ||
| self.sugar = sugar | ||
| self.spice = spice | ||
| self.metabolism_sugar = metabolism_sugar | ||
| self.metabolism_spice = metabolism_spice | ||
|
|
||
| self.memory = STLTMemory( | ||
| agent=self, | ||
| display=True, | ||
| llm_model="openai/gpt-4o-mini", | ||
| ) | ||
|
|
||
| self.tool_manager = trader_tool_manager | ||
|
|
||
| self.system_prompt = ( | ||
| "You are a Trader agent in a Sugarscape simulation. " | ||
| "You need Sugar and Spice to survive. " | ||
| "If your MRS (Marginal Rate of Substitution) is high, you desperately need Sugar. " | ||
| "If MRS is low, you need Spice. " | ||
| "You can move to harvest resources or trade with neighbors." | ||
| ) | ||
|
|
||
| self.update_internal_metrics() | ||
|
|
||
| def calculate_mrs(self): | ||
| if self.sugar == 0: | ||
| return 100.0 | ||
|
|
||
| if self.metabolism_sugar == 0: | ||
| return 100.0 | ||
|
|
||
| if self.metabolism_spice == 0: | ||
| return 0.0 | ||
|
|
||
| return (self.spice / self.metabolism_spice) / ( | ||
| self.sugar / self.metabolism_sugar | ||
| ) | ||
|
|
||
| def update_internal_metrics(self): | ||
| mrs = self.calculate_mrs() | ||
|
|
||
| self.internal_state = [ | ||
| s | ||
| for s in self.internal_state | ||
| if not any(x in s for x in ["Sugar", "Spice", "MRS", "WARNING_"]) | ||
| ] | ||
|
|
||
| self.internal_state.append(f"My Sugar inventory is: {self.sugar}") | ||
| self.internal_state.append(f"My Spice inventory is: {self.spice}") | ||
| self.internal_state.append( | ||
| f"My Marginal Rate of Substitution (MRS) is {mrs:.2f}" | ||
| ) | ||
|
|
||
| if self.sugar < self.metabolism_sugar * 2: | ||
| self.internal_state.append( | ||
| "WARNING: I am in danger of starvation from lack of sugar" | ||
| ) | ||
| if self.spice < self.metabolism_spice * 2: | ||
| self.internal_state.append( | ||
| "WARNING: I am in danger of starvation from lack of spice" | ||
| ) | ||
|
|
||
| def step(self): | ||
| self.sugar -= self.metabolism_sugar | ||
| self.spice -= self.metabolism_spice | ||
|
|
||
| if self.sugar <= 0 or self.spice <= 0: | ||
| self.model.grid.remove_agent(self) | ||
| self.remove() | ||
| return | ||
|
|
||
| self.update_internal_metrics() | ||
|
|
||
| observation = self.generate_obs() | ||
|
|
||
| plan = self.reasoning.plan( | ||
| obs=observation, | ||
| selected_tools=["move_to_best_resource", "propose_trade"], | ||
| ) | ||
|
|
||
| self.apply_plan(plan) | ||
|
|
||
| async def astep(self): | ||
| self.sugar -= self.metabolism_sugar | ||
| self.spice -= self.metabolism_spice | ||
|
|
||
| if self.sugar <= 0 or self.spice <= 0: | ||
| self.model.grid.remove_agent(self) | ||
| self.remove() | ||
| return | ||
|
|
||
| self.update_internal_metrics() | ||
| observation = self.generate_obs() | ||
|
|
||
| plan = await self.reasoning.aplan( | ||
| obs=observation, | ||
| selected_tools=["move_to_best_resource", "propose_trade"], | ||
| ) | ||
| self.apply_plan(plan) | ||
|
|
||
|
|
||
| class Resource(mesa.discrete_space.CellAgent): | ||
| def __init__(self, model, max_capacity=10, current_amount=10, growback=1): | ||
| super().__init__(model=model) | ||
|
|
||
| self.max_capacity = max_capacity | ||
| self.current_amount = current_amount | ||
| self.growback = growback | ||
|
|
||
| self.internal_state = [] | ||
|
|
||
| self.tool_manager = resource_tool_manager | ||
|
|
||
| def step(self): | ||
| if self.current_amount < self.max_capacity: | ||
| self.current_amount += self.growback | ||
| if self.current_amount > self.max_capacity: | ||
| self.current_amount = self.max_capacity | ||
|
|
||
| async def astep(self): | ||
| self.step() | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am assuming you want to represent sugar and spice as Resource agents, in which case you should consider adding an attribute here that makes it possible to classify the instances of resource.