-
Notifications
You must be signed in to change notification settings - Fork 163
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
Mesa implementation of Ising Model #138
base: main
Are you sure you want to change the base?
Changes from 4 commits
2925aac
ec724b3
ff3b7fa
d60038e
df3b9e0
e137a60
b5ad740
72cfb9e
fe7f71e
17c65ce
68b6d31
352ac32
ebb7976
78b8a36
cc65481
923a83e
fbf80af
4dac7dc
487561d
86cdf99
3e0d16f
274a1bc
bd17361
642a9ad
8e0ccf6
dbedf6f
4b66a64
c0ea5a2
1fa7a25
0c6366c
0ebc4d1
43622b9
2b944ff
2fd8945
7ecbb93
eed4f7d
c2f4a73
efffb67
49342ea
4fe5c71
bbf5ad2
269ff1a
3c71ea9
6e59b9a
1d67252
2bc3a2d
3e5ebd1
2ece0cf
541b004
aee22c1
a0d50a8
875110a
eb66dae
be57796
fc18ece
0fd7f82
3d29e4c
dee6363
89f9379
6312ebe
f24626e
679b677
330b4d9
aca30a2
e7345de
831be82
c4b4c75
ec07d89
660102f
d79f74e
2b43544
9c30387
31537d8
9a5396f
8d0c722
ed6f910
561f1f1
ecb8aab
44a3ed5
7d75683
1a01780
cd2eb61
0704d13
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Ising Model | ||
|
||
## Summary | ||
|
||
The Ising model (or Lenz–Ising model), named after the physicists Ernst Ising and Wilhelm Lenz, is a mathematical model of ferromagnetism in statistical mechanics. The model consists of discrete variables that represent magnetic dipole moments of atomic "spins" that can be in one of two states (+1 or −1). The spins are arranged in a graph, usually a lattice (where the local structure repeats periodically in all directions), allowing each spin to interact with its neighbors. Neighboring spins that agree have a lower energy than those that disagree; the system tends to the lowest energy but heat disturbs this tendency, thus creating the possibility of different structural phases. The model allows the identification of phase transitions as a simplified model of reality. The two-dimensional square-lattice Ising model is one of the simplest statistical models to show a phase transition. | ||
|
||
## How to Run | ||
|
||
To run the model interactively, run ``solara run run.py`` in this directory. e.g. | ||
|
||
Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press ``run``. | ||
|
||
## Things to Try | ||
|
||
What happens when the temperature slider is very high? (This is called the "paramagnetic" state.) Try this with different **Spin Up Probability** values. | ||
|
||
What happens when the temperature slider is set very low? (This is called the "ferromagnetic" state.) Again, try this with different **Spin Up Probability** values. | ||
|
||
Between these two very different behaviors is a transition point. On an infinite grid, the transition point can be proved to be $2 / ln (1 + sqrt 2)$, which is about 2.27. On a large enough finite toroidal grid, the transition point is near this number. | ||
|
||
## References | ||
|
||
(NetLogo Ising)[https://ccl.northwestern.edu/netlogo/models/Ising] | ||
|
||
(Introduction to Monte Carlo methods for an Ising Model of a Ferromagnet)[https://arxiv.org/pdf/0803.0217] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import mesa | ||
import numpy as np | ||
|
||
from .spin import Spin | ||
|
||
|
||
class IsingModel(mesa.Model): | ||
def __init__( | ||
self, | ||
width=50, | ||
height=50, | ||
spin_up_probability: float = 0.7, | ||
temperature: float = 1, | ||
): | ||
super().__init__() | ||
self.temperature = temperature | ||
self.grid = mesa.space.SingleGrid(width, height, torus=True) | ||
|
||
for contents, (x, y) in self.grid.coord_iter(): | ||
cell = Spin((x, y), self, Spin.DOWN) | ||
if self.random.random() < spin_up_probability: | ||
cell.state = cell.UP | ||
self.grid.place_agent(cell, (x, y)) | ||
|
||
self.running = True | ||
|
||
def step(self): | ||
agents_list = list(self.agents) | ||
self._steps += 1000 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand this correctly, you are using _step to count the number of agent activations (you use 1000, as in the for loop). Why use |
||
for i in range(1000): | ||
random_spin = self.random.choice(agents_list) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having to create a new list of agents from scratch and selecting one is not ideal. I know this is a limitation of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks for the comment. it does not feel right at all haha. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently I don’t think there is. @vitorfrois could you open a new issue on the main Mesa repo describing this lack of functionality and linking to this PR as example? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was going to suggest you can just do: self.agents.select(n=1000) But looking at our current implementation (here) that isn’t actually random. What you can do though is: self.agents.shuffle().select(n=1000) |
||
dE = self.get_energy_change(random_spin) | ||
if dE < 0: | ||
vitorfrois marked this conversation as resolved.
Show resolved
Hide resolved
|
||
random_spin.state *= -1 | ||
else: | ||
if self.random.random() < self.boltzmann(dE): | ||
random_spin.state *= -1 | ||
|
||
def get_energy_change(self, spin: Spin): | ||
neighbors = spin.neighbors() | ||
sum_over_neighbors = 0 | ||
for neighbor in neighbors: | ||
sum_over_neighbors += neighbor.state | ||
return sum_over_neighbors * 2 * spin.state | ||
|
||
def boltzmann(self, dE): | ||
vitorfrois marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return np.exp(-dE / self.temperature) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
def portraySpin(spin): | ||
vitorfrois marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
This function is registered with the visualization server to be called | ||
each tick to indicate how to draw the cell in its current state. | ||
:param cell: the cell in the simulation | ||
:return: the portrayal dictionary. | ||
""" | ||
if spin is None: | ||
raise AssertionError | ||
return { | ||
"marker": "s", | ||
vitorfrois marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"x": spin.x, | ||
"y": spin.y, | ||
"color": "grey" if spin.state is spin.UP else "black", | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import mesa | ||
|
||
|
||
class Spin(mesa.Agent): | ||
"""Represents a single ALIVE or DEAD cell in the simulation.""" | ||
|
||
UP = 1 | ||
DOWN = -1 | ||
|
||
def __init__(self, pos, model, init_state): | ||
""" | ||
Create a cell, in the given state, at the given x, y position. | ||
""" | ||
super().__init__(pos, model) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn’t this map to If intended, please add a comment, if not, update There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pos is a tuple |
||
self.x, self.y = pos | ||
self.state = init_state | ||
|
||
def neighbors(self): | ||
return self.model.grid.iter_neighbors((self.x, self.y), True) | ||
vitorfrois marked this conversation as resolved.
Show resolved
Hide resolved
|
vitorfrois marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import solara | ||
from ising.model import IsingModel | ||
from ising.portrayal import portraySpin | ||
from mesa.visualization import JupyterViz | ||
vitorfrois marked this conversation as resolved.
Show resolved
Hide resolved
|
||
from mesa.visualization.UserParam import Slider | ||
|
||
model_params = { | ||
"temperature": Slider( | ||
label="Temperature", value=2, min=0.01, max=10, step=0.06, dtype=float | ||
), | ||
"spin_up_probability": Slider( | ||
label="Spin Up Probability", | ||
value=0.5, | ||
min=0, | ||
max=1, | ||
step=0.05, | ||
), | ||
} | ||
|
||
|
||
@solara.component | ||
def Page(): | ||
JupyterViz( | ||
vitorfrois marked this conversation as resolved.
Show resolved
Hide resolved
|
||
IsingModel, | ||
model_params, | ||
name="Ising Model", | ||
agent_portrayal=portraySpin, | ||
) |
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.
We don't have means of aggregating various param sweep into a single plot of magnetization vs temperature. This is a homework for us developers to allow visualizing batch_run result easily.
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.
Agreed. Some related tickets: