NetLogo Efficient way to create fixed number of links - social-networking

I have about 5000 agents (people) in my model. I want to give them an arbitrary number of friends and have reciprocal but random pairing. So if person A chooses person B then person B also chooses person A. My code works fine, but is fairly slow. I will likely want to increase both the number of friends and the number of people in the future. Any quicker suggestions?
ask people
[ let new-links friends - count my-links
if new-links > 0
[ let candidates other people with [ count my-links < friends ]
create-links-with n-of min (list new-links count candidates) candidates
[ hide-link ]
]
]
Note that friends is a global variable in the above code, but my eventual code will probably generalise to have wanted-number-of-friends as an attribute of people.
EDITED Added if new-links > 0 condition so that the nested ask is avoided when no candidates need to be found. This improved speed but still not really scaleable.

Great question. This is actually quite challenging to optimize. The problematic line is:
let candidates other people with [ count my-links < friends ]
This is slow because it has every agent checking with every other agent. With 5000 agents, that's 25,000,000 checks! Unfortunately, there isn't really a good way to optimize this particular line without some fancy data structures.
Fortunately, there is a solution that generalizes really well to generating any degree distribution in the network (which it sounds like that's what you ultimately want). Unfortunately, the solution doesn't translate super well to NetLogo. Here it is though:
let pairs [] ;; pairs will hold a pairs of turtles to be linked
while [ pairs = [] ] [ ;; we might mess up creating these pairs (by making self loops), so we might need to try a couple of times
let half-pairs reduce sentence [ n-values friends [ self ] ] of turtles ;; create a big list where each turtle appears once for each friend it wants to have
set pairs (map list half-pairs shuffle half-pairs) ;; pair off the items of half-pairs with a randomized version of half-pairs, so we end up with a list like: [[ turtle 0 turtle 5 ] [ turtle 0 turtle 376 ] ... [ turtle 1 turtle 18 ]]
;; make sure that no turtle is paired with itself
if not empty? filter [ first ? = last ? ] pairs [
set pairs []
]
]
;; now that we have pairs that we know work, create the links
foreach pairs [
ask first ? [
create-link-with last ?
]
]
It doesn't matter if friends here is a global or a turtle variable. The amount of time this takes depends on the number of times that it needs to try making pairs, which is random. Experimenting, I found that it was usually about 3 seconds with 5000 agents, each with degree 5. This is compared to about 60 seconds on my machine with your original way of doing this (which, for what it's worth, is the way I would recommend when using fewer agents).

After debugging (see NetLogo Efficiently create network with arbitrary degree distribution), the following version is relatively efficient. It constructs an agentset (called lonely below) for the turtles that still need links and deletes them as they get enough links. Removing individual turtles is more efficient than the nested process to create the candidate set each time.
The variable nFriends is a global (with a slider in the original model) that is the target number of links, identical for all agents.
let lonely turtles with [count my-links < nFriends]
ask turtles
[ set lonely other lonely
let new-links nFriends - count my-links
if new-links > 0
[ let chosen n-of min (list new-links count lonely) lonely
create-links-with chosen [ hide-link ]
ask chosen [ if count my-links = nFriends [ set lonely other lonely ] ]
]
]

Related

Random matrix with determinant not zero in Maxima

I want to generate a matrix with random entries such that the determinant of that matrix is not zero using Maxima and further down the line implement this in STACK for Moodle. I am completely new to working with Maxima (or any CAS for that matter), so I have been going through various examples I found online and have so far managed to get this:
Generating a 2x2 random matrix with 0 or 1 (for simplicity reasons) and calculating its determinant:
g[i,j]:=1-random(2);
M1:genmatrix(g,2,2);
dM1:determinant(M1);
For the next step I wanted to define a matrix M2 as follows:
M2:(if dM1#0 then M1 else ***)
If the determinant of the matrix M1 is already not zero, fine, I'll go with that, but I am struggling with the else-part. I was thinking of creating a loop that generates new random numbers g[i,j] for M1 until I get a matrix with determinant not zero, but am unsure on how to do that or if there are other options.
In addition: as I mentioned this is ultimately something I want to implement in STACK for moodle (question will be to solve a system of linear equations with the generated matrix being the matrix of this system), so I don't know if there are any limitations on using if and while loops in STACK, so if somebody is aware of known problems I would appreciate any input.
You can say for ... do ... return(something) to yield something from the for-loop, which can be assigned to a variable. In this case it looks like this works as intended:
(%i9) M2: for i thru 10
do (genmatrix (lambda ([i, j], 1 - random(2)), 2, 2),
if determinant(%%) # 0 then return(%%));
[ 1 0 ]
(%o9) [ ]
[ 0 1 ]
(%i10) M2: for i thru 10
do (genmatrix (lambda ([i, j], 1 - random(2)), 2, 2),
if determinant(%%) # 0 then return(%%));
[ 1 0 ]
(%o10) [ ]
[ 1 1 ]
(%i11) M2: for i thru 10
do (genmatrix (lambda ([i, j], 1 - random(2)), 2, 2),
if determinant(%%) # 0 then return(%%));
[ 1 1 ]
(%o11) [ ]
[ 0 1 ]
Note that the first argument for genmatrix is a lambda expression (i.e. unnamed function). If you put the name of an array function such as g in your example, it will not have the intended effect, because in Maxima, array functions are memoizing functions, giving a stored output for an input that has been seen before. Obviously that's not intended if the output is supposed to be random.
Note also that M2 will be assigned done if the for-loop runs to completion without finding a non-singular matrix. I think that's useful, since you can see if M2 # 'done to ensure that you did get a result.
Finally note that it makes a difference to use the "group of expressions without local variables" (...) as the body of the for-loop, instead of "group of expressions with local variables" block(...), because the effect of return is different in those two cases.

NetLogo: Assign Turtles Randomly but Equally to Different Groups

I used the code below to create 50 turtles and randomly assign them to one of four different strategies (i.e. a, b, c and d):
The problem is that when I decrease the number of created turtles or when I increase the number of strategies, I face a situation where at least one of the strategies is not taken by any turtle.
turtles-own [ my_strategy ]
to setup
;; create 50 turtles and assign them randomly
;; to one of four different strategies
create-turtles 50 [
set my_strategy one-of [ "a" "b" "c" "d" ]
]
end
I need your help here to:
1. Make sure that I do not face a situation where one or more strategies are not taken by any turtle.
2. Make sure that the number of turtles assigned to each strategy is roughly equal.
I tried to solve the problem by using the code below, but it did not work:
turtles-own [ my_strategy ]
to setup
let strategies [ "a" "b" "c" "d" ]
let turtles-num 51
let i 0
create-turtles turtles-num
while [ not any? turtles with [ my_strategy = 0 ] ] [
ifelse i < length strategies - 1 [ set i i + 1 ] [ set i 0 ]
ask n-of ceiling ( turtles-num / length strategies ) turtles with [ my_strategy = 0 ] [
set my_strategy item i strategies
]
]
Thank you for your help.
In general, you should never use who numbers for anything in NetLogo. However, this is one of the very few occasions where it's appropriate.
From comments, you actually want equal (or as close to equal as possible) numbers in each group so you don't need to calculate the number in each group. When turtles are created, they are created with sequential who numbers. So you can use the mod operator to assign them to each strategy in turn.
turtles-own [ my_strategy ]
to setup
;; create 50 turtles and assign them equally
;; to one of four different strategies
create-turtles 50 [
set my_strategy item (who mod 4) [ "a" "b" "c" "d" ]
]
end

How to limit the number of links an agent can make in a model?

I'm setting up a model with a number of agents who are connected via links as follows:
ask turtles [create-links-with turtles in-radius vision with [self != myself]]
But I want to be able to limit the number of connections that an individual agent can make. I've tried a few things but to no avail.
Hope you can help.
You can get a randomly selected subset of turtles to link to using the n-of primitive like this:
ask turtles [create-links-with n-of 3 turtles in-radius vision with [self != myself]]
However, you will need to do something a bit trickier if you want a firm upper limit because this doesn't stop other turtles from creating links to the same turtle(s). If you want a fixed number of links (5 in example below), you can do this:
ask turtles
[ let new-links 5 - count my-links
let candidates other turtles with [ count my-links < 5 ]
create-links-with n-of min (list new-links count candidates) candidates
[ ... ]
]
If you simply want an upper limit, you could ask any turtles with my-links > limit to randomly select the appropriate number of links to delete. So, after creating links, something like this (not tested):
ask turtles with [count my-links > LIMIT]
[ if count my-links > LIMIT [ask n-of (count my-links - LIMIT) my-links [die]] ]

Sort a patch set or agentset with two conditions in NetLogo

My patches have cost and gain attributes, and I would like to sort a list of patches with the minimum cost and the maximum gain. The sort-by function works for sorting on one attribute but how can I sort on both attributes?
To sort an agentset on many attributes, you can use either sort-by or sort-on:
patches-own [ cost gain ]
to sort-patches
ca
ask patches [
set cost random 100
set gain random 100
]
let patches-sorted-by sort-by [
([ cost ] of ?1 > [ cost ] of ?2) or
([ cost ] of ?1 = [ cost ] of ?2 and [ gain ] of ?1 < [ gain ] of ?2)
] patches
show map [[ list cost gain ] of ? ] patches-sorted-by
let patches-sorted-on sort-on [ (cost * -1000) + gain ] patches
show map [[ list cost gain ] of ? ] patches-sorted-on
end
Which one you prefer is up to you. Using sort-on requires to carefully construct your formula (i.e., the above would not work if you can have gains greater than 1000) but is slightly less verbose.
Edit: a more general way of sorting on multiple criteria
OK, this is probably overkill for your situation, but I came up with something a lot more general:
to-report sort-by-criteria [ criteria items ]
; `criteria` needs to be a task that returns a list of numbers
report sort-by [
compare-lists (runresult criteria ?1) (runresult criteria ?2)
] items
end
to-report compare-lists [ l1 l2 ]
report ifelse-value (empty? l1 or empty? l2) [ false ] [
ifelse-value (first l1 = first l2)
[ compare-lists but-first l1 but-first l2 ]
[ first l1 < first l2 ]
]
end
What you need to pass sort-by-criteria is a task that, given one of the items that you want to sort, will report a list of numbers according to which your items will be sorted.
In your case, you would use it like:
let sorted-patches sort-by-criteria (
task [[ list (-1 * cost) gain ] of ? ]
) patches
For two criteria, it's probably not worth using, but if you had a long list of criteria, it would probably be a lot easier and clearer to use than any of the other methods.

calculate the mean over a set amount of ticks

I want the observer to calculate the mean of the number of floodings of a house (=patch) over the last 10 years (=ticks) when a certain action takes place (in this case, an insurance application by a turtle). This occurrence doesn't occur regularly, it can be regarded as a random occurrence (more or less).
So basically, I need some code that calculates the mean of floodings over the last 10 ticks when insurance application occurs.
Assuming floodings is a patch-variable and you'd like to determine the mean number of floodings for a given patch:
patches-own [
floodings
floodingsHistory
floodingsMean10
]
; At the end of each tick, patches store the current
; number of floodings in a list:
ask patches [ set floodingsHistory fput floodings floodingsHistory ]
; In case of [insurance application] patches (or a certain patch) calculate
; the mean over a sublist that only comprises the values of the latest 10 ticks:
ask patches [ set floodingsMean10 mean (sublist floodingsHistory 0 10) ]

Resources