Adding a code suite to run active information storage and transfer entropy analysis of positional / heading data from swarms/flocks/schools, including a demonstration of how to use the suite with a NetLogo example. Documentation on a wiki page to follow later ...

This commit is contained in:
Joseph Lizier 2019-08-29 23:08:16 +10:00
parent c22e37d1ab
commit 4966fdabc2
11 changed files with 1969 additions and 0 deletions

View File

@ -0,0 +1,740 @@
;; Adapted by J.T. Lizier from the original CC BY NC SA 3.0 licensed Flocking model from NetLogo
;; and released under the same license.
;; See license info on the Info tab (and below if viewing text file)
globals [
headings-file
positionsx-file
positionsy-file
sorted-agents
]
turtles-own [
flockmates ;; agentset of nearby turtles
nearest-neighbor ;; closest one of our flockmates
]
to setup
clear-all
create-turtles population
[ set color yellow - 2 + random 7 ;; random shades look nice
set size 1.5 ;; easier to see
setxy random-xcor random-ycor
set flockmates no-turtles ]
reset-ticks
;; Set up for file output of data:
set headings-file "headings.txt"
set positionsx-file "positionsx.txt"
set positionsy-file "positionsy.txt"
if file-exists? headings-file [
file-delete headings-file ;; clear file
]
if file-exists? positionsx-file [
file-delete positionsx-file ;; clear file
]
if file-exists? positionsy-file [
file-delete positionsy-file ;; clear file
]
set sorted-agents sort turtles
end
to go
ask turtles [ flock ]
;; the following line is used to make the turtles
;; animate more smoothly.
repeat 5 [ ask turtles [ fd 0.2 ] display ]
;; for greater efficiency, at the expense of smooth
;; animation, substitute the following line instead:
;; ask turtles [ fd 1 ]
capture-data ;; write the current position+heading data of turtles to file
tick
end
to flock ;; turtle procedure
find-flockmates
if any? flockmates
[ find-nearest-neighbor
ifelse distance nearest-neighbor < minimum-separation
[ separate ]
[ align
cohere ] ]
end
to find-flockmates ;; turtle procedure
set flockmates other turtles in-radius vision
end
to find-nearest-neighbor ;; turtle procedure
set nearest-neighbor min-one-of flockmates [distance myself]
end
;;; SEPARATE
to separate ;; turtle procedure
turn-away ([heading] of nearest-neighbor) max-separate-turn
end
;;; ALIGN
to align ;; turtle procedure
turn-towards average-flockmate-heading max-align-turn
end
to-report average-flockmate-heading ;; turtle procedure
;; We can't just average the heading variables here.
;; For example, the average of 1 and 359 should be 0,
;; not 180. So we have to use trigonometry.
let x-component sum [dx] of flockmates
let y-component sum [dy] of flockmates
ifelse x-component = 0 and y-component = 0
[ report heading ]
[ report atan x-component y-component ]
end
;;; COHERE
to cohere ;; turtle procedure
turn-towards average-heading-towards-flockmates max-cohere-turn
end
to-report average-heading-towards-flockmates ;; turtle procedure
;; "towards myself" gives us the heading from the other turtle
;; to me, but we want the heading from me to the other turtle,
;; so we add 180
let x-component mean [sin (towards myself + 180)] of flockmates
let y-component mean [cos (towards myself + 180)] of flockmates
ifelse x-component = 0 and y-component = 0
[ report heading ]
[ report atan x-component y-component ]
end
;;; HELPER PROCEDURES
to turn-towards [new-heading max-turn] ;; turtle procedure
turn-at-most (subtract-headings new-heading heading) max-turn
end
to turn-away [new-heading max-turn] ;; turtle procedure
turn-at-most (subtract-headings heading new-heading) max-turn
end
;; turn right by "turn" degrees (or left if "turn" is negative),
;; but never turn more than "max-turn" degrees
to turn-at-most [turn max-turn] ;; turtle procedure
ifelse abs turn > max-turn
[ ifelse turn > 0
[ rt max-turn ]
[ lt max-turn ] ]
[ rt turn ]
end
to capture-data
;; To do a one-off:
;; file-open "headings.txt"
;; ;; Does not select turtles in order:
;; ask turtles [ file-print heading ]
;; file-close
if ticks > 4000
[ stop ]
;; Write the current headings
file-open headings-file ;; Opening file for writing
foreach sorted-agents [
[the-turtle] ->
ask the-turtle [
file-write heading
]
]
file-print " " ;; Terminate the line
file-close
;; Write the x positions
file-open positionsx-file ;; Opening file for writing
foreach sorted-agents [
[the-turtle] ->
ask the-turtle [
file-write xcor
]
]
file-print " " ;; Terminate the line
file-close
;; Write the y positions
file-open positionsy-file ;; Opening file for writing
foreach sorted-agents [
[the-turtle] ->
ask the-turtle [
file-write ycor
]
]
file-print " " ;; Terminate the line
file-close
end
; Copyright 1998 Uri Wilensky.
; See Info tab for full copyright and license.
@#$#@#$#@
GRAPHICS-WINDOW
250
10
755
516
-1
-1
7.0
1
10
1
1
1
0
1
1
1
-35
35
-35
35
1
1
1
ticks
30.0
BUTTON
39
93
116
126
NIL
setup
NIL
1
T
OBSERVER
NIL
NIL
NIL
NIL
1
BUTTON
122
93
203
126
NIL
go
T
1
T
OBSERVER
NIL
NIL
NIL
NIL
0
SLIDER
9
51
232
84
population
population
1.0
1000.0
300.0
1.0
1
NIL
HORIZONTAL
SLIDER
4
217
237
250
max-align-turn
max-align-turn
0.0
20.0
5.0
0.25
1
degrees
HORIZONTAL
SLIDER
4
251
237
284
max-cohere-turn
max-cohere-turn
0.0
20.0
3.0
0.25
1
degrees
HORIZONTAL
SLIDER
4
285
237
318
max-separate-turn
max-separate-turn
0.0
20.0
1.5
0.25
1
degrees
HORIZONTAL
SLIDER
9
135
232
168
vision
vision
0.0
10.0
3.0
0.5
1
patches
HORIZONTAL
SLIDER
9
169
232
202
minimum-separation
minimum-separation
0.0
5.0
1.0
0.25
1
patches
HORIZONTAL
@#$#@#$#@
## WHAT IS IT?
This model is an attempt to mimic the flocking of birds. (The resulting motion also resembles schools of fish.) The flocks that appear in this model are not created or led in any way by special leader birds. Rather, each bird is following exactly the same set of rules, from which flocks emerge.
## HOW IT WORKS
The birds follow three rules: "alignment", "separation", and "cohesion".
"Alignment" means that a bird tends to turn so that it is moving in the same direction that nearby birds are moving.
"Separation" means that a bird will turn to avoid another bird which gets too close.
"Cohesion" means that a bird will move towards other nearby birds (unless another bird is too close).
When two birds are too close, the "separation" rule overrides the other two, which are deactivated until the minimum separation is achieved.
The three rules affect only the bird's heading. Each bird always moves forward at the same constant speed.
## HOW TO USE IT
First, determine the number of birds you want in the simulation and set the POPULATION slider to that value. Press SETUP to create the birds, and press GO to have them start flying around.
The default settings for the sliders will produce reasonably good flocking behavior. However, you can play with them to get variations:
Three TURN-ANGLE sliders control the maximum angle a bird can turn as a result of each rule.
VISION is the distance that each bird can see 360 degrees around it.
## THINGS TO NOTICE
Central to the model is the observation that flocks form without a leader.
There are no random numbers used in this model, except to position the birds initially. The fluid, lifelike behavior of the birds is produced entirely by deterministic rules.
Also, notice that each flock is dynamic. A flock, once together, is not guaranteed to keep all of its members. Why do you think this is?
After running the model for a while, all of the birds have approximately the same heading. Why?
Sometimes a bird breaks away from its flock. How does this happen? You may need to slow down the model or run it step by step in order to observe this phenomenon.
## THINGS TO TRY
Play with the sliders to see if you can get tighter flocks, looser flocks, fewer flocks, more flocks, more or less splitting and joining of flocks, more or less rearranging of birds within flocks, etc.
You can turn off a rule entirely by setting that rule's angle slider to zero. Is one rule by itself enough to produce at least some flocking? What about two rules? What's missing from the resulting behavior when you leave out each rule?
Will running the model for a long time produce a static flock? Or will the birds never settle down to an unchanging formation? Remember, there are no random numbers used in this model.
## EXTENDING THE MODEL
Currently the birds can "see" all around them. What happens if birds can only see in front of them? The `in-cone` primitive can be used for this.
Is there some way to get V-shaped flocks, like migrating geese?
What happens if you put walls around the edges of the world that the birds can't fly into?
Can you get the birds to fly around obstacles in the middle of the world?
What would happen if you gave the birds different velocities? For example, you could make birds that are not near other birds fly faster to catch up to the flock. Or, you could simulate the diminished air resistance that birds experience when flying together by making them fly faster when in a group.
Are there other interesting ways you can make the birds different from each other? There could be random variation in the population, or you could have distinct "species" of bird.
## NETLOGO FEATURES
Notice the need for the `subtract-headings` primitive and special procedure for averaging groups of headings. Just subtracting the numbers, or averaging the numbers, doesn't give you the results you'd expect, because of the discontinuity where headings wrap back to 0 once they reach 360.
## RELATED MODELS
* Moths
* Flocking Vee Formation
* Flocking - Alternative Visualizations
## CREDITS AND REFERENCES
(Note: This is an adaptation by J.T. Lizier of the original Flocking model distributed in the NetLogo Models Library, under CC BY NC SA license (see below))
This model is inspired by the Boids simulation invented by Craig Reynolds. The algorithm we use here is roughly similar to the original Boids algorithm, but it is not the same. The exact details of the algorithm tend not to matter very much -- as long as you have alignment, separation, and cohesion, you will usually get flocking behavior resembling that produced by Reynolds' original model. Information on Boids is available at http://www.red3d.com/cwr/boids/.
## HOW TO CITE
If you mention this model or the NetLogo software in a publication, we ask that you include the citations below.
For the model itself:
* Wilensky, U. (1998). NetLogo Flocking model. http://ccl.northwestern.edu/netlogo/models/Flocking. Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL.
Please cite the NetLogo software as:
* Wilensky, U. (1999). NetLogo. http://ccl.northwestern.edu/netlogo/. Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL.
## COPYRIGHT AND LICENSE
Copyright 1998 Uri Wilensky.
![CC BY-NC-SA 3.0](http://ccl.northwestern.edu/images/creativecommons/byncsa.png)
This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License. To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
Commercial licenses are also available. To inquire about commercial licenses, please contact Uri Wilensky at uri@northwestern.edu.
This model was created as part of the project: CONNECTED MATHEMATICS: MAKING SENSE OF COMPLEX PHENOMENA THROUGH BUILDING OBJECT-BASED PARALLEL MODELS (OBPML). The project gratefully acknowledges the support of the National Science Foundation (Applications of Advanced Technologies Program) -- grant numbers RED #9552950 and REC #9632612.
This model was converted to NetLogo as part of the projects: PARTICIPATORY SIMULATIONS: NETWORK-BASED DESIGN FOR SYSTEMS LEARNING IN CLASSROOMS and/or INTEGRATED SIMULATION AND MODELING ENVIRONMENT. The project gratefully acknowledges the support of the National Science Foundation (REPP & ROLE programs) -- grant numbers REC #9814682 and REC-0126227. Converted from StarLogoT to NetLogo, 2002.
<!-- 1998 2002 -->
@#$#@#$#@
default
true
0
Polygon -7500403 true true 150 5 40 250 150 205 260 250
airplane
true
0
Polygon -7500403 true true 150 0 135 15 120 60 120 105 15 165 15 195 120 180 135 240 105 270 120 285 150 270 180 285 210 270 165 240 180 180 285 195 285 165 180 105 180 60 165 15
arrow
true
0
Polygon -7500403 true true 150 0 0 150 105 150 105 293 195 293 195 150 300 150
box
false
0
Polygon -7500403 true true 150 285 285 225 285 75 150 135
Polygon -7500403 true true 150 135 15 75 150 15 285 75
Polygon -7500403 true true 15 75 15 225 150 285 150 135
Line -16777216 false 150 285 150 135
Line -16777216 false 150 135 15 75
Line -16777216 false 150 135 285 75
bug
true
0
Circle -7500403 true true 96 182 108
Circle -7500403 true true 110 127 80
Circle -7500403 true true 110 75 80
Line -7500403 true 150 100 80 30
Line -7500403 true 150 100 220 30
butterfly
true
0
Polygon -7500403 true true 150 165 209 199 225 225 225 255 195 270 165 255 150 240
Polygon -7500403 true true 150 165 89 198 75 225 75 255 105 270 135 255 150 240
Polygon -7500403 true true 139 148 100 105 55 90 25 90 10 105 10 135 25 180 40 195 85 194 139 163
Polygon -7500403 true true 162 150 200 105 245 90 275 90 290 105 290 135 275 180 260 195 215 195 162 165
Polygon -16777216 true false 150 255 135 225 120 150 135 120 150 105 165 120 180 150 165 225
Circle -16777216 true false 135 90 30
Line -16777216 false 150 105 195 60
Line -16777216 false 150 105 105 60
car
false
0
Polygon -7500403 true true 300 180 279 164 261 144 240 135 226 132 213 106 203 84 185 63 159 50 135 50 75 60 0 150 0 165 0 225 300 225 300 180
Circle -16777216 true false 180 180 90
Circle -16777216 true false 30 180 90
Polygon -16777216 true false 162 80 132 78 134 135 209 135 194 105 189 96 180 89
Circle -7500403 true true 47 195 58
Circle -7500403 true true 195 195 58
circle
false
0
Circle -7500403 true true 0 0 300
circle 2
false
0
Circle -7500403 true true 0 0 300
Circle -16777216 true false 30 30 240
cow
false
0
Polygon -7500403 true true 200 193 197 249 179 249 177 196 166 187 140 189 93 191 78 179 72 211 49 209 48 181 37 149 25 120 25 89 45 72 103 84 179 75 198 76 252 64 272 81 293 103 285 121 255 121 242 118 224 167
Polygon -7500403 true true 73 210 86 251 62 249 48 208
Polygon -7500403 true true 25 114 16 195 9 204 23 213 25 200 39 123
cylinder
false
0
Circle -7500403 true true 0 0 300
dot
false
0
Circle -7500403 true true 90 90 120
face happy
false
0
Circle -7500403 true true 8 8 285
Circle -16777216 true false 60 75 60
Circle -16777216 true false 180 75 60
Polygon -16777216 true false 150 255 90 239 62 213 47 191 67 179 90 203 109 218 150 225 192 218 210 203 227 181 251 194 236 217 212 240
face neutral
false
0
Circle -7500403 true true 8 7 285
Circle -16777216 true false 60 75 60
Circle -16777216 true false 180 75 60
Rectangle -16777216 true false 60 195 240 225
face sad
false
0
Circle -7500403 true true 8 8 285
Circle -16777216 true false 60 75 60
Circle -16777216 true false 180 75 60
Polygon -16777216 true false 150 168 90 184 62 210 47 232 67 244 90 220 109 205 150 198 192 205 210 220 227 242 251 229 236 206 212 183
fish
false
0
Polygon -1 true false 44 131 21 87 15 86 0 120 15 150 0 180 13 214 20 212 45 166
Polygon -1 true false 135 195 119 235 95 218 76 210 46 204 60 165
Polygon -1 true false 75 45 83 77 71 103 86 114 166 78 135 60
Polygon -7500403 true true 30 136 151 77 226 81 280 119 292 146 292 160 287 170 270 195 195 210 151 212 30 166
Circle -16777216 true false 215 106 30
flag
false
0
Rectangle -7500403 true true 60 15 75 300
Polygon -7500403 true true 90 150 270 90 90 30
Line -7500403 true 75 135 90 135
Line -7500403 true 75 45 90 45
flower
false
0
Polygon -10899396 true false 135 120 165 165 180 210 180 240 150 300 165 300 195 240 195 195 165 135
Circle -7500403 true true 85 132 38
Circle -7500403 true true 130 147 38
Circle -7500403 true true 192 85 38
Circle -7500403 true true 85 40 38
Circle -7500403 true true 177 40 38
Circle -7500403 true true 177 132 38
Circle -7500403 true true 70 85 38
Circle -7500403 true true 130 25 38
Circle -7500403 true true 96 51 108
Circle -16777216 true false 113 68 74
Polygon -10899396 true false 189 233 219 188 249 173 279 188 234 218
Polygon -10899396 true false 180 255 150 210 105 210 75 240 135 240
house
false
0
Rectangle -7500403 true true 45 120 255 285
Rectangle -16777216 true false 120 210 180 285
Polygon -7500403 true true 15 120 150 15 285 120
Line -16777216 false 30 120 270 120
leaf
false
0
Polygon -7500403 true true 150 210 135 195 120 210 60 210 30 195 60 180 60 165 15 135 30 120 15 105 40 104 45 90 60 90 90 105 105 120 120 120 105 60 120 60 135 30 150 15 165 30 180 60 195 60 180 120 195 120 210 105 240 90 255 90 263 104 285 105 270 120 285 135 240 165 240 180 270 195 240 210 180 210 165 195
Polygon -7500403 true true 135 195 135 240 120 255 105 255 105 285 135 285 165 240 165 195
line
true
0
Line -7500403 true 150 0 150 300
line half
true
0
Line -7500403 true 150 0 150 150
pentagon
false
0
Polygon -7500403 true true 150 15 15 120 60 285 240 285 285 120
person
false
0
Circle -7500403 true true 110 5 80
Polygon -7500403 true true 105 90 120 195 90 285 105 300 135 300 150 225 165 300 195 300 210 285 180 195 195 90
Rectangle -7500403 true true 127 79 172 94
Polygon -7500403 true true 195 90 240 150 225 180 165 105
Polygon -7500403 true true 105 90 60 150 75 180 135 105
plant
false
0
Rectangle -7500403 true true 135 90 165 300
Polygon -7500403 true true 135 255 90 210 45 195 75 255 135 285
Polygon -7500403 true true 165 255 210 210 255 195 225 255 165 285
Polygon -7500403 true true 135 180 90 135 45 120 75 180 135 210
Polygon -7500403 true true 165 180 165 210 225 180 255 120 210 135
Polygon -7500403 true true 135 105 90 60 45 45 75 105 135 135
Polygon -7500403 true true 165 105 165 135 225 105 255 45 210 60
Polygon -7500403 true true 135 90 120 45 150 15 180 45 165 90
square
false
0
Rectangle -7500403 true true 30 30 270 270
square 2
false
0
Rectangle -7500403 true true 30 30 270 270
Rectangle -16777216 true false 60 60 240 240
star
false
0
Polygon -7500403 true true 151 1 185 108 298 108 207 175 242 282 151 216 59 282 94 175 3 108 116 108
target
false
0
Circle -7500403 true true 0 0 300
Circle -16777216 true false 30 30 240
Circle -7500403 true true 60 60 180
Circle -16777216 true false 90 90 120
Circle -7500403 true true 120 120 60
tree
false
0
Circle -7500403 true true 118 3 94
Rectangle -6459832 true false 120 195 180 300
Circle -7500403 true true 65 21 108
Circle -7500403 true true 116 41 127
Circle -7500403 true true 45 90 120
Circle -7500403 true true 104 74 152
triangle
false
0
Polygon -7500403 true true 150 30 15 255 285 255
triangle 2
false
0
Polygon -7500403 true true 150 30 15 255 285 255
Polygon -16777216 true false 151 99 225 223 75 224
truck
false
0
Rectangle -7500403 true true 4 45 195 187
Polygon -7500403 true true 296 193 296 150 259 134 244 104 208 104 207 194
Rectangle -1 true false 195 60 195 105
Polygon -16777216 true false 238 112 252 141 219 141 218 112
Circle -16777216 true false 234 174 42
Rectangle -7500403 true true 181 185 214 194
Circle -16777216 true false 144 174 42
Circle -16777216 true false 24 174 42
Circle -7500403 false true 24 174 42
Circle -7500403 false true 144 174 42
Circle -7500403 false true 234 174 42
turtle
true
0
Polygon -10899396 true false 215 204 240 233 246 254 228 266 215 252 193 210
Polygon -10899396 true false 195 90 225 75 245 75 260 89 269 108 261 124 240 105 225 105 210 105
Polygon -10899396 true false 105 90 75 75 55 75 40 89 31 108 39 124 60 105 75 105 90 105
Polygon -10899396 true false 132 85 134 64 107 51 108 17 150 2 192 18 192 52 169 65 172 87
Polygon -10899396 true false 85 204 60 233 54 254 72 266 85 252 107 210
Polygon -7500403 true true 119 75 179 75 209 101 224 135 220 225 175 261 128 261 81 224 74 135 88 99
wheel
false
0
Circle -7500403 true true 3 3 294
Circle -16777216 true false 30 30 240
Line -7500403 true 150 285 150 15
Line -7500403 true 15 150 285 150
Circle -7500403 true true 120 120 60
Line -7500403 true 216 40 79 269
Line -7500403 true 40 84 269 221
Line -7500403 true 40 216 269 79
Line -7500403 true 84 40 221 269
x
false
0
Polygon -7500403 true true 270 75 225 30 30 225 75 270
Polygon -7500403 true true 30 75 75 30 270 225 225 270
@#$#@#$#@
NetLogo 6.0
@#$#@#$#@
set population 200
setup
repeat 200 [ go ]
@#$#@#$#@
@#$#@#$#@
@#$#@#$#@
@#$#@#$#@
default
0.0
-0.2 0 0.0 1.0
0.0 1 1.0 0.0
0.2 0 0.0 1.0
link direction
true
0
Line -7500403 true 150 150 90 180
Line -7500403 true 150 150 210 180
@#$#@#$#@
0
@#$#@#$#@

View File

@ -0,0 +1,103 @@
%
% This script loads properties for transfer entropy analysis of the data from
% the NetLogo Flocking model.
%
% Author: Joseph T. Lizier, 2019
%
%%
%% Java Information Dynamics Toolkit (JIDT)
%% Copyright (C) 2019, Joseph T. Lizier
%%
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
%%
% This script loads the default properties for the transfer entropy processing:
clear('properties');
%%%%%%%%%
% FILENAMES
%%%%%%%%%
% Input data files:
% properties.files can be:
% a. a cell array of file names, e.g.: {'file1.xlsx', 'file2.xlsx'}
% b. a call to ls or ls with an argument, e.g. ls('*.xlsx')
% c. a space or tab separated character row vector of file names
% d. a character matrix of filenames (each filename on a separate row)
properties.files = 'positions%s-small.txt';
% Function to read in the data files:
% loadScript must point to a function .m file that accepts two arguments
% (the name of a file, and properties object) and returns [x,y,z] (z optional, only when 3D)
% data where each is an array, e.g. x(time, fishIndex) indexed first by time and second by fish index.
% Use the name of the .m file after an "@" character:
properties.loadScript = @loadseparatexy;
% Is the data returned by the loadScript 3D (true) or 2D (false)?
properties.data3d = false;
% Results file - will hold the parsed velocities / relative positions, plus the
% local transfer entropy results
properties.resultsFile = 'results.mat';
%%%%%%%%%
% PARAMETERS
%%%%%%%%%
% Distance within which to consider a pair for the info theoretic analysis (units are as per what is used in the data files)
properties.pairRange = 4; % These ones have a causal range of 3
%%%%%%%%%
% INFORMATION THEORETIC Parameters
% Only lag is used for computing lagged mutual information
% All lag, k and tau are used for transfer entropy
% k - embedding dimension of the past of the destination array.
% tau - embedding delay: time cycles separating each element in the past of the destination.
% lag - time delay between the source and target in cycles
% You can set kRange, tauRange and lagRange to ask that these are optimised by runAnalysis:
% properties.kRange = 1:10;
% properties.tauRange = 1:4;
% properties.lagRange = 1:10;
% You can also set k, tau and lag to values that generateObservations should use
% (although note that if this is called via runAnalysis then it will overwrite them):
properties.k = 1;
properties.tau = 1;
properties.lag = 1;
% Do we include the relative source position in the transfer entropy calculation (true), or
% only the relative source heading (false)
properties.includeSourcePositionInTransfer = false;
% Do we take relative source heading and position with respect to dest heading at that same
% time point (true, this is what we did for Crosato paper) or with respect
% to dest heading just previous to state update (false)?
properties.sourceWrtSameDestTime = true;
% JIDT location:
properties.jidtJarLocation = '../../../../infodynamics.jar';
% Which estimator to use.
% Valid values are 'gaussian' (linear) or 'kraskov' (non-linear)
% properties.estimator = 'gaussian';
properties.estimator = 'kraskov';
% Properties for JIDT estimators:
properties.jidt.kNNs = 4; % Number of nearest neighbours for Kraskov algorithm: just use 4 (default)
properties.jidt.autoDynamicCorrelationExclusion = true; % Exclude nearest neighbours from at least the same target transition from being included in counts for TE
properties.aisNumSurrogates = 0; % Number of surrogate calculations to run for AIS (just to see the noise floor. 0 means skip)
properties.teNumSurrogates = 0; % Number of surrogate calculations to run for TE (just to see the noise floor. 0 means skip)

View File

@ -0,0 +1,73 @@
function ais = computeAIS(D, Dpast, properties)
% Computes active information storage from the pre-processed velocity data
%
% Author: Joseph T. Lizier, 2019
%
% Inputs:
% - D - target samples (may be multivariate as per generateObservations)
% - Dpast - target past samples (multivariate, and embedded up to k previous samples)
% - properties (required) - object with properties for the calculations,
% with sub-members as specificied in the loadProperties.m file. If not supplied
% the properties are loaded from loadProperties.m
%
% Outputs:
% - ais - active information storage value (MI between D and Dpast)
%%
%% Java Information Dynamics Toolkit (JIDT)
%% Copyright (C) 2019, Joseph T. Lizier et al.
%%
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
%%
% -- STEP 0 : create java object
% check out http://lizier.me/joseph/software/jidt/javadocs/v1.3/
% for the description of all classes and methods
javaaddpath(properties.jidtJarLocation); % add JIDT path
% Select correct MI class for this estimator type
if (strcmp('kraskov', properties.estimator))
MI_CLASS = 'infodynamics.measures.continuous.kraskov.MutualInfoCalculatorMultiVariateKraskov1';
else
MI_CLASS = 'infodynamics.measures.continuous.gaussian.MutualInfoCalculatorMultiVariateGaussian';
end
% Compute the information stored in the past of the target.
% Creates java object of the given class
AIScalculator = javaObject(MI_CLASS);
% -- STEP 1 : set properties
AIScalculator.setProperty('k', num2str(properties.jidt.kNNs));
AIScalculator.setProperty('BIAS_CORRECTION', 'true'); % Used for Gaussian only
% -- STEP 2 : initialise
% here the parameters are the dimensionality of the series
% in this case taken directly from the number of columns in each variable
AIScalculator.initialise(size(D,2), size(Dpast,2));
% -- STEP 3 : add in observations
AIScalculator.setObservations(D, Dpast);
% -- STEP 4 : compute the local AIS (will be bias-corrected now for either Gaussian or KSG)
ais = AIScalculator.computeAverageLocalOfObservations(); % global (average) value
fprintf('Mean AIS_%s (k=%d,tau=%d) = %.3f\n', properties.estimator, properties.k, properties.tau, ais);
if (properties.aisNumSurrogates > 0)
% Compute the (statistical significance via) null distribution empirically (e.g. with 100 permutations),
% and use this for empirical bias correction (otherwise we're relying on analytic)
aisMeasDist = AIScalculator.computeSignificance(properties.aisNumSurrogates);
fprintf('Null distribution: %.4f +/- %.4f std dev.; p(surrogate > measured)=%.3f from %d surrogates)\n', ...
aisMeasDist.getMeanOfDistribution(), aisMeasDist.getStdOfDistribution(), ...
aisMeasDist.pValue, properties.aisNumSurrogates);
ais = ais - aisMeasDist.getMeanOfDistribution();
fprintf('Bias corrected Mean AIS_%s (k=%d,tau=%d) = %.3f\n', properties.estimator, properties.k, properties.tau, ais);
end
end

View File

@ -0,0 +1,101 @@
function tranEntropy = computeTE(S, D, Dpast, properties)
% Computes transfer entropy from the pre-processed velocity data
%
% Author: Emanuele Crosato, Joseph T. Lizier, 2019
%
% Inputs:
% - S - source samples (may be multivariate as per generate3DObservations)
% - D - target samples (may be multivariate as per generate3DObservations)
% - Dpast - target past samples (multivariate, and embedded up to k previous samples)
% - properties (required) - object with properties for the calculations,
% with sub-members as specificied in the loadProperties.m file. If not supplied
% the properties are loaded from loadProperties.m
%
% Outputs:
% - te - transfer entropy value (conditional MI from S (maybe plus RelSourcePos) to D given Dpast). If not requested, then
% the te (and an array of local values) is saved to properties.resultsFile
%%
%% Java Information Dynamics Toolkit (JIDT)
%% Copyright (C) 2019, Joseph T. Lizier et al.
%%
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
%%
% -- STEP 0 : create java object
% check out http://lizier.me/joseph/software/jidt/javadocs/v1.3/
% for the description of all classes and methods
javaaddpath(properties.jidtJarLocation); % add JIDT path
% the following is the java class for conditional mutual information in JIDT
% transfer entropy is infact mutual information conditioning on the past
% of the destination
if (strcmp('kraskov', properties.estimator))
CMI_CLASS = 'infodynamics.measures.continuous.kraskov.ConditionalMutualInfoCalculatorMultiVariateKraskov1';
else
CMI_CLASS = 'infodynamics.measures.continuous.gaussian.ConditionalMutualInfoCalculatorMultiVariateGaussian';
end
TEcalculator = javaObject(CMI_CLASS); % creates a java object of the given class
% -- STEP 1 : set properties
TEcalculator.setProperty('k', num2str(properties.jidt.kNNs));
TEcalculator.setProperty('BIAS_CORRECTION', 'true'); % Used for Gaussian only
if (isfield(properties.jidt, 'dynamicCorrelationExclusion'))
% We'll ensure samples from the same target transition aren't included in nearest neighbour counts
% (it will exclude some others as well, but this only adds some small noise to the calculation)
TEcalculator.setProperty('DYN_CORR_EXCL', num2str(properties.jidt.dynamicCorrelationExclusion));
end
% -- STEP 2 : initialise
% here the parameters are the dimensionality of the series
% in this case taken directly from the number of columns in each variable
TEcalculator.initialise(size(S,2), size(D,2), size(Dpast,2));
% -- STEP 3 : add in observations
TEcalculator.setObservations(S, D, Dpast);
% -- STEP 4 : compute the local entropies
tranEntropy = TEcalculator.computeAverageLocalOfObservations(); % global (average) value
fprintf('Mean TE_%s (k=%d,tau=%d,lag=%d) = %.3f\n', ...
properties.estimator, properties.k, properties.tau, properties.lag, tranEntropy);
if (properties.teNumSurrogates > 0)
% Compute the (statistical significance via) null distribution empirically (e.g. with 100 permutations):
measDist = TEcalculator.computeSignificance(properties.teNumSurrogates);
fprintf('Null distribution: %.4f +/- %.4f std dev.; p(surrogate > measured)=%.5f from %d surrogates)\n', ...
measDist.getMeanOfDistribution(), measDist.getStdOfDistribution(), ...
measDist.pValue, properties.teNumSurrogates);
pValue = measDist.pValue;
meanOfSurrogates = measDist.getMeanOfDistribution();
stdOfSurrogates = measDist.getStdOfDistribution();
else
pValue = 1;
meanOfSurrogates = 0;
stdOfSurrogates = 0;
end
if (nargout >= 1)
% Supply the samples back to the caller
% (the caller is probably trying to optimise parameters at the moment)
% Nothing to do then actually...
else
% We're going to save the results instead
% First generate the local values to save as well:
localTranEntropy = TEcalculator.computeLocalOfPreviousObservations(); % local values
% save results
save(properties.resultsFile, 'tranEntropy', 'localTranEntropy', 'pValue', 'meanOfSurrogates', 'stdOfSurrogates', '-append');
fprintf('Transfer entropy saved in %s\n', properties.resultsFile);
end
end

View File

@ -0,0 +1,378 @@
function [D, Dpast, S, RelSourcePos, maxSourceSamplesForATarget] = generateObservations(properties)
% This function generates the observations from which
% we can then compute information dynamics with JIDT.
% This will work for either 2D or 3D samples (as specified by the properties)
%
% Author: Emanuele Crosato, Joseph T. Lizier, 2019
%
% Inputs:
% - properties (required) - object with properties for the calculations,
% with sub-members as specificied in the loadProperties.m file.
%
% Outputs:
% - D - target samples (may be multivariate as per below)
% - Dpast - target past samples (multivariate, and embedded up to k previous samples)
% - S - source relative headings samples (may be multivariate as per below)
% - RelSourcePos - relative source position (may be multivariate as below)
% - maxSourceSamplesForATarget - maximum number of sources in range for a specific target sample. Used for dynamic correlation exclusion externally for TE calculations.
% If no outputs are requested, these are saved to properties.resultsFile
%%
%% Java Information Dynamics Toolkit (JIDT)
%% Copyright (C) 2019, Joseph T. Lizier et al.
%%
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
%%
% Call internal utility to put filename lists in a common format
files = processFilenames(properties.files);
% initialize series for storing the samples:
S = []; % initialise source observations
D = []; % initialise destination observations
Dpast = []; % initialise destination past observations
% only S and D are necessary for lagged mutual information
% all S, D and Dpast are necessary for transfer entropy
fileTimeAndPair = []; % to save time and pair
RelSourcePos = []; % initialise relative source positions
sample = 1; % initialise the current sample number
maxSourceSamplesForATarget = 0; % Track the maximum number of in range sources for a given target sample
% Need to loop over fileIndex rather than for file = files (which doesn't work properly for cell array of length 1)
for fileIndex = 1:length(files)
dataFileName = files{fileIndex};
% load preprocessed data using the function specified in properties.loadScript
if (properties.data3d)
[x,y,z] = feval(properties.loadScript, dataFileName, properties);
numMissing = sum(sum(isnan([x,y,z])));
else
[x,y] = feval(properties.loadScript, dataFileName, properties);
numMissing = sum(sum(isnan([x,y])));
end
fprintf('Loading data in %s (%d missing values)\n', dataFileName, numMissing);
samplesBeforeThisFile = sample;
% Translate the raw positions into delta Positions
velX = x(2:end,:) - x(1:end-1,:);
velY = y(2:end,:) - y(1:end-1,:);
if (properties.data3d)
velZ = z(2:end,:) - z(1:end-1,:);
end
% Translate velocities into headings:
if (properties.data3d)
% Spherical polars:
[headingXY,headingZ,speed] = cart2sph(velX,velY,velZ);
else
% Polar coordinates:
[headingXY,speed] = cart2pol(velX, velY);
end
% Manual way is (can verify these are same except for Nans on x,y and z):
% headingXY = atan(velY ./ velX) + (((velX < 0).*(velY>0)) .* pi) + ...
% (((velX < 0).*(velY<=0)) .* (- pi));
% But the above fails if x and y are *both* zero leaving Nan:
% velYOnVelX = velY ./ velX; % Don't replace all nans in headingXY as some mean missing values
% headingXY(isnan(velYOnVelX(:))) = 0;
% xyMagnitude = sqrt(velX.^2 + velY.^2);
% Since xyMagnitude can only be positive, we can take the straight atan to
% compute headingZ
% headingZ = atan(velZ ./ xyMagnitude);
% get number of fish and update time cycles
numCycles = size(velX,1);
numFish = size(velX,2);
% Initialising destPastSample is only important in terms of ensuring it is a row vector.
% If we have 3D data, the vector will get padded out to the appropriate length with the first sample below.
destPastSample = zeros(1, properties.k);
startTime = max(1+properties.lag, (properties.k-1)*properties.tau + 3); % Adding 3: one for target, one for first target past, one for taking differences
for i = startTime : numCycles % cycle over time
timePointForSourceHeading = i-properties.lag; % This is indexed into velX and headingXY, hence no extra +1 !
timePointForSourcePosition = i+1-properties.lag; % This is indexed into x not velX, hence the extra +1 !
% Compute relative position of source (at time timePointForSourcePosition) to
% target either at this same time step or the current time at which it is updating.
% Note this position difference is relative to absolute Cartesian coordinates
% (we'll convert to relative to source heading later):
if (properties.sourceWrtSameDestTime)
destPositionTimePointRef = timePointForSourcePosition;
else
destPositionTimePointRef = i;
end
% cycle over fish pairs
for idxFD = 1 : numFish % Target/Destination
% check destination variable
if isnan(headingXY(i,idxFD)) || isnan(headingXY(i-1,idxFD))
continue;
end
if properties.data3d && (isnan(headingZ(i,idxFD)) || isnan(headingZ(i-1,idxFD)))
continue;
end
% check destination past vector
missingFound = false;
for h = 1 : properties.k
idx = i-1-(h-1)*properties.tau;
if isnan(headingXY(idx,idxFD)) || isnan(headingXY(idx-1,idxFD))
missingFound = true;
break;
end
if properties.data3d && (isnan(headingZ(idx,idxFD)) || isnan(headingZ(idx-1,idxFD)))
missingFound = true;
break;
end
end
if missingFound
continue;
end
% Postcondition: All destination variables are ok
% We will create **source** observation as source headings relative to target headings at appropriate time point:
if (properties.sourceWrtSameDestTime)
% Take reference dest heading at same time as source:
theta_FDXY_ref = headingXY(timePointForSourceHeading,idxFD);
else
% Take reference dest heading at prev time step:
theta_FDXY_ref = headingXY(i-1,idxFD);
end
if properties.data3d
if (properties.sourceWrtSameDestTime)
theta_FDZ_ref = headingZ(timePointForSourceHeading,idxFD);
else
theta_FDZ_ref = headingZ(i-1,idxFD);
end
end
% create **destination** observation as change in headings:
theta_FDXY_curr = headingXY(i,idxFD);
theta_FDXY_prev = headingXY(i-1,idxFD);
if properties.data3d
theta_FDZ_curr = headingZ(i,idxFD);
theta_FDZ_prev = headingZ(i-1,idxFD);
destSample = [angleDifference(theta_FDXY_curr, theta_FDXY_prev), ...
angleDifference(theta_FDZ_curr, theta_FDZ_prev)];
else
destSample = angleDifference(theta_FDXY_curr, theta_FDXY_prev);
end
% create **destination past** observation as changes in headings at each step:
% TODO: we could take differences to previous sample amongst the k rather than only
% one back from each sample: I'm not sure if this would be a more wholistic embedding or not
% (only makes a difference if tau>1)
DpastColIndex = 1;
for h = 1 : properties.k
idx = i-1-(h-1)*properties.tau;
theta_FDXY_curr = headingXY(idx,idxFD);
theta_FDXY_prev = headingXY(idx-1,idxFD);
destPastSample(DpastColIndex) = angleDifference(theta_FDXY_curr, theta_FDXY_prev);
DpastColIndex = DpastColIndex + 1;
if properties.data3d
theta_FDZ_curr = headingZ(idx,idxFD);
theta_FDZ_prev = headingZ(idx-1,idxFD);
destPastSample(DpastColIndex) = angleDifference(theta_FDZ_curr, theta_FDZ_prev);
DpastColIndex = DpastColIndex + 1;
end
end
if (isfield(properties, 'destSamplesOnly'))
if (properties.destSamplesOnly)
% User has asked for [D,Dpast] samples only to be returned,
% so we can do these now (without looping over sources):
% Fill in the destination and destination next samples now from above:
D(sample, :) = destSample;
Dpast(sample, :) = destPastSample;
fileTimeAndPair(sample,:) = [fileIndex i idxFD nan];
% increment sample number
sample = sample + 1;
continue; % skip looping over the sources
end
end
numSourceSamplesForThisTarget = 0;
for idxFS = 1 : numFish % Source
% check not the same fish
if (idxFD == idxFS)
continue;
end
relXOfSource = x(timePointForSourcePosition,idxFS) - x(destPositionTimePointRef,idxFD);
relYOfSource = y(timePointForSourcePosition,idxFS) - y(destPositionTimePointRef,idxFD);
if (properties.data3d)
relZOfSource = z(timePointForSourcePosition,idxFS) - z(destPositionTimePointRef,idxFD);
[xyAbsoluteAngleOfSource,zAbsoluteAngleOfSource,distanceBetween] = ...
cart2sph(relXOfSource,relYOfSource,relZOfSource);
else
[xyAbsoluteAngleOfSource,distanceBetween] = ...
cart2pol(relXOfSource,relYOfSource);
end
% Manually: (verified this matches cart2sph):
% xyAbsoluteAngleOfSource = atan(relYOfSource ./ relXOfSource) + ...
% (((relXOfSource < 0).*(relYOfSource>0)) .* pi) + ...
% (((relXOfSource < 0).*(relYOfSource<=0)) .* (- pi));
% xyRelMagnitude = sqrt(relXOfSource.^2 + relYOfSource.^2);
% zAbsoluteAngleOfSource = atan(relZOfSource ./ xyRelMagnitude);
% distanceBetween = sqrt(relXOfSource.^2 + relYOfSource.^2 + ...
% relZOfSource.^2);
% check in range
if (distanceBetween > properties.pairRange)
continue;
end
% check source variable
if isnan(headingXY(timePointForSourceHeading,idxFD)) || isnan(headingXY(timePointForSourceHeading,idxFS))
continue;
end
if properties.data3d && (isnan(headingZ(timePointForSourceHeading,idxFD)) || isnan(headingZ(timePointForSourceHeading,idxFS)))
continue;
end
% Postcondition: There are no missing headings so we can generate an observation.
numSourceSamplesForThisTarget = numSourceSamplesForThisTarget + 1;
% Now compose the data that will be saved for this sample:
% fileTimeAndPair is [file_index, time_index, target_index, source_index]
fileTimeAndPair(sample,:) = [fileIndex i idxFD idxFS];
% create **source** observation as source headings relative to target headings at appropriate time point:
theta_FSXY_lag = headingXY(timePointForSourceHeading,idxFS);
% And convert the absolute angular positions into
% relative angular positions compared to the target's
% heading.
xyRelativeAngleOfSource = angleDifference(...
xyAbsoluteAngleOfSource, ...
theta_FDXY_ref);
if properties.data3d
theta_FSZ_lag = headingZ(timePointForSourceHeading,idxFS);
sourceSample = [angleDifference(theta_FDXY_ref, theta_FSXY_lag), ...
angleDifference(theta_FDZ_ref, theta_FSZ_lag)];
% Elevation angle differences need to be in -pi/2,pi/2 range
zRelativeAngleOfSource = angleDifferencePiOn2(...
zAbsoluteAngleOfSource, ...
theta_FDZ_ref);
% Store these relative polar coordinates of source at timePointForSourcePosition
RelSourcePos(sample,:) = [distanceBetween, xyRelativeAngleOfSource, zRelativeAngleOfSource];
else
sourceSample = angleDifference(theta_FDXY_ref, theta_FSXY_lag);
% Store these relative polar coordinates of source at timePointForSourcePosition
RelSourcePos(sample,:) = [distanceBetween, xyRelativeAngleOfSource];
end
if properties.includeSourcePositionInTransfer
S(sample,:) = [sourceSample, RelSourcePos];
else
S(sample,:) = sourceSample;
end
% Fill in the destination and destination next samples now from above:
D(sample, :) = destSample;
Dpast(sample, :) = destPastSample;
% increment sample number
sample = sample + 1;
end
if (numSourceSamplesForThisTarget > maxSourceSamplesForATarget)
maxSourceSamplesForATarget = numSourceSamplesForThisTarget;
end
end
% fprintf('Run time step %d\n', i);
end
fprintf(' added %d samples\n', sample - samplesBeforeThisFile);
end
if (sample - 1 == 0)
% We've added no samples
warning('No samples added for the given parameters!');
end
if (nargout > 1)
% Supply the samples back to the caller
% (the caller is probably trying to optimise parameters at the moment)
% Nothing to do then actually...
else
% We're going to save the samples instead
% display to check
fprintf('Displaying first 5 samples for source, target, target past and fileTimeAndPair as a check:');
disp(S(1:5,:));
disp(D(1:5,:));
disp(Dpast(1:5,:));
disp(fileTimeAndPair(1:5,:));
% input(prompt);
% save series and properties
save(properties.resultsFile, 'S', 'D', 'Dpast', 'files', 'fileTimeAndPair', 'RelSourcePos', ...
'maxSourceSamplesForATarget', 'properties');
fprintf('Series saved in %s (%d samples in total)\n', properties.resultsFile, sample - 1);
end
end
% End function
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% function for computing the difference between two angles.
% the differrence must be between pi and -pi
function [diff] = angleDifference(angleA, angleB)
diff = angleA - angleB; % subtract angles
if abs(diff) > pi % if absolute value is larger than pi
% replace with the complementary angle and switch sign
diff = (2*pi - abs(diff) ) * (-sign(diff));
end
end
% function for computing the difference between two angles in [-pi/2,pi/2].
% the differrence must be returned between pi/2 and -pi/2.
% This is used for differences in elevation angles
function [diff] = angleDifferencePiOn2(angleA, angleB)
diff = angleA - angleB; % subtract angles
% Pre-condition: differences between angles which were in range of
% [-pi/2,pi/2] can only be in range [-pi,pi]
if (diff > pi/2)
diff = pi/2 - (diff - pi/2);
elseif (diff < -pi/2)
diff = -pi/2 + (-pi/2 - diff);
end
end
% Turns the fileList into a cell array of file names. The fileList can be either:
% a. a cell array of file names, e.g.: {'file1.xlsx', 'file2.xlsx'}
% b. a call to ls or ls with an argument, e.g. ls('*.xlsx')
% c. a space or tab separated character row vector of file names
% d. a character matrix of filenames (each filename on a separate row)
function fileCellArray = processFilenames(fileList)
if (iscell(fileList))
% We're done already:
fileCellArray = fileList;
elseif (isvector(fileList))
% We have a row vector of space/tab separate filenames:
fileCellArray = strsplit(strtrim(fileList)); % extra strtrim to remove trailing \n's
elseif (ismatrix(fileList))
fileCellArray = {};
for r = 1 : size(fileList, 1)
fileCellArray{r} = strtrim(fileList(r,:));
end
else
error('fileList appears to be of an incorrect format\n');
end
end

View File

@ -0,0 +1,66 @@
function [posX,posY] = loadBasic2d(dataFileName, properties)
% This script loads the raw data from a .txt file,
% preprocesses the data and save it as a .mat file.
% The txt data is assumed to have timestamp in column 1,
% then fish 1's X and Y coordinates in columns 2 and 3,
% then fish 2's X and Y coordinates in columns 4 and 5,
% and so on.
%%
%% Java Information Dynamics Toolkit (JIDT)
%% Copyright (C) 2019, Joseph T. Lizier et al.
%%
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
%%
% prompt = 'Press a key to continue';
%%% LOAD RAW DATA %%%
%fprintf('Loading data in %s\n', dataFileName); % print to check
% input(prompt);
% read the .txt as a Matlab table (assuming .txt is tab-separated)
data = load(dataFileName);
%fprintf('Size of data is %d - %d\n', ... % print table's size - %d is for int
% size(data,1), size(data,2)); % see also %f (real) and %s (string)
% disp(data(1:5,:)); % display first 10 rows to check
% input(prompt);
%%% FORMAT DATA IN A MORE CONVENIENT WAY %%%
numFish = (size(data,2)-1) ./ 2; % number of fish (we know it from the raw data)
numCycles = size(data,1); % number of time steps (we also know it)
fprintf('Number of fish %d and cycles %d\n', numFish, numCycles);
% prepare variables for x and y position
% as a table [numFish x numCycles]
posX = nan(numCycles,numFish);
posY = nan(numCycles,numFish);
% fill the position tables
for f = 1 : numFish % cycle over all fish
% copy into new variables
posX(:,f) = data(:,2+(f-1)*2);
posY(:,f) = data(:,3+(f-1)*2);
end
% display to check
% disp(size(posX));
% disp(posX(1:5,:));
% disp(size(posY));
% disp(posY(1:5,:));
% input(prompt);

View File

@ -0,0 +1,40 @@
function [x,y] = loadseparatexy(filename, properties)
% loadseparatexy loads 2D fish data from 2 separate txt files (one for x, one for y)
% where in each file time increases down the
% rows and then across the columns we have position columns for each
% fish in turn, i.e. in position x file:
% <fish1x>, <fish2x>, <fish3x>, etc
%
% Inputs:
% - filename - the name template of the file to load, with %s where 'x' and 'y' should be filled in
% - properties (not required) - properties object (may be required for other file loaders)
% Outputs:
% - x - 2D array, each row contains x position for each fish (in columns)
% - y - as per x
%%
%% Java Information Dynamics Toolkit (JIDT)
%% Copyright (C) 2019, Joseph T. Lizier et al.
%%
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
%%
xfilename = sprintf(filename, 'x');
x = load(xfilename);
yfilename = sprintf(filename, 'y');
y = load(yfilename);
end

View File

@ -0,0 +1,43 @@
function [x,y,z] = loadseparatexy(filename, properties)
% loadseparatexy loads 3D fish data from 3 separate txt files (one for x, one for y, one for z)
% where in each file time increases down the
% rows and then across the columns we have position columns for each
% fish in turn, i.e. in position x file:
% <fish1x>, <fish2x>, <fish3x>, etc
%
% Inputs:
% - filename - the name template of the file to load, with %s where 'x', 'y' and 'z' should be filled in
% - properties (not required) - properties object (may be required for other file loaders)
% Outputs:
% - x - 2D array, each row contains x position for each fish (in columns)
% - y - as per x
% - z - as per x
%%
%% Java Information Dynamics Toolkit (JIDT)
%% Copyright (C) 2019, Joseph T. Lizier et al.
%%
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
%%
xfilename = sprintf(filename, 'x');
x = load(xfilename);
yfilename = sprintf(filename, 'y');
y = load(yfilename);
zfilename = sprintf(filename, 'z');
z = load(zfilename);
end

View File

@ -0,0 +1,45 @@
function [x,y,z] = loadxls3d(filename, properties)
% loadxls3d loads 3D fish data from an xls file where time increases down the
% rows and then across the columns we have 3 x,y,z position columns for each
% fish in turn, i.e.:
% <timestamp1>, <fish1x>, <fish1y>, <fish1z>, <fish2x>, <fish2y>, <fish2z>, etc
% <timestamp2>, <fish1x>, <fish1y>, <fish1z>, <fish2x>, <fish2y>, <fish2z>, etc
%
% Inputs:
% - filename - the name of the file to load
% - properties (not required) - properties object (may be required for other file loaders)
% Outputs:
% - x - 2D array, each row contains x position for each fish (in columns)
% - y - as per x
% - z - as per x
%%
%% Java Information Dynamics Toolkit (JIDT)
%% Copyright (C) 2019, Joseph T. Lizier et al.
%%
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
%%
[data,txt,raw] = xlsread(filename);
% Make sure we preprocess data to have x,y, and z as 2D arrays of x(timeStep, fishID):
% timeSteps = size(data,1); % number of rows; not required
xFishIndex = 2 : 3 : size(data,2); % which indices are the x values for different fish
x = data(:,xFishIndex);
y = data(:,xFishIndex+1);
z = data(:,xFishIndex+2);
end

View File

@ -0,0 +1,206 @@
function plotLocalTEs(properties)
% Plot the local TEs to show where the information transfer hotspots are from target fish relative to each source
%
% Author: Joseph T. Lizier, 2019
%
% Inputs:
% - properties (required) - object with properties for the calculations,
% with sub-members as specificied in the loadProperties.m file. If not supplied
% the properties are loaded from loadProperties.m
%%
%% Java Information Dynamics Toolkit (JIDT)
%% Copyright (C) 2019, Joseph T. Lizier et al.
%%
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
%%
if (nargin < 1)
fprintf('No properties object supplied, attempting to load properties via a loadProperties script ...');
% By default, just try to load properties locally
if (exist('loadProperties') == 2)
% there is a loadProperties script
loadProperties;
else
% there is not a loadProperties script
error('No properties object supplied, and no loadProperties script found.');
end
end
load(properties.resultsFile);
% Loads:
% S -- source samples
% D -- target samples
% Dpast -- target past samples (embedded up to k previous samples)
% files -- cell array of file names that we took samples from
% fileTimeAndPair -- each row holds file index, time index, target index, source index
% RelSourcePos -- each row holds distance between the pair for this sample,
% their xyRelativeAngleOfSource, and zRelativeAngleOfSource
% lag -- source-target lag that is in use
% k -- embedding length for target that is in use
% tau -- embedding delay for target that is in use
% pairRange -- range within which we've pulled source-target interactions
% tranEntropy -- average transfer entropy
% localTranEntropy -- local TE for each sample
fprintf('%d samples in total for %d fish\n', length(S), length(unique(fileTimeAndPair(:,3))));
relDistance = RelSourcePos(:,1);
relTheta = RelSourcePos(:,2);
% I think this is giving us the right conversions:
if (properties.data3d)
relPhi = RelSourcePos(:,3);
distXY = relDistance .* cos(relPhi);
distZ = relDistance .* sin(relPhi);
else
distXY = relDistance;
end
distInFront = distXY .* cos(relTheta);
distToLeft = distXY .* sin(relTheta);
% Plot where all the raw positions are:
% Will need to turn this off when we have too many
figure()
% polar(relTheta, distXY, '.r'); % This is equivalent to below:
scatter(distInFront, distToLeft, 2, localTranEntropy);
title('Relative position of source in XY plane for target heading, coloured for TE');
colorbar;
% Plot the density of samples
makePolarBinnedPlot(relTheta, distXY, ones(length(relTheta), 1), 12, 10, true, false);
title('Density of samples in each bin (r_{XY},\theta)');
% Plot the TE in XY plane
makePolarBinnedPlot(relTheta, distXY, localTranEntropy, 12, 10, true, true);
title('Average TE in each bin (r_{XY},\theta)');
% Plot raw positions in phi-z:
% figure()
% polar(relPhi, relDistance, '.r');
% title('Relative position of source in Z-phi plane for target heading');
if (properties.data3d)
% Plot the density of samples in distance-phi plane
makePolarBinnedPlot(relPhi, relDistance, ones(length(relTheta), 1), 12, 10, true, false);
title('Density of samples in each bin (r, \phi)');
% Plot the TE in distance-phi plane
makePolarBinnedPlot(relPhi, relDistance, localTranEntropy, 12, 10, true, true);
title('Average TE in each bin (r, \phi)');
end
end
% Inputs:
% - thetas - angles for each sample
% - radii - radius for each sample
% - numAngleBins - how many bins to make across 2*pi
% - numRadialBins - how many bins to make up to the maximum radii
% - useMaxEntBinning - whether to make bins with approx same numbers of points (true)
% or same size (false)
% - plotMean - if true (default) plot the mean within each bin, else plot the total (divded by area)
% The latter is used for densities for example
function makePolarBinnedPlot(thetas, radii, valuesToPlot, numAngleBins, numRadialBins, useMaxEntBinning, plotMean)
if (nargin < 6)
useMaxEntBinning = false;
end
if (nargin < 7)
plotMean = true;
end
if ((min(thetas) < -pi/2) || (max(thetas) > pi/2))
% We're using full angular range -pi : pi
minAngle = -pi;
maxAngle = pi;
extraBinForPlotWrap = true;
else
% We're only using -pi/2:pi/2
minAngle = -pi/2;
maxAngle = pi/2;
extraBinForPlotWrap = false;
end
angleStep = (maxAngle - minAngle) / numAngleBins;
radiusStep = max(radii) / numRadialBins;
% Simple way to do the binning for even bins:
% binnedAngles = floor(thetas ./ angleStep); % Gives the discrete bin for the angle
% binnedRadii = floor(radii ./ radiusStep); % Gives the discrete bin for the radius
% More general, and allowing bins to spread with points:
if (useMaxEntBinning)
% Space the bins for roughly same
% numbers of points (when examined marginally):
sortedAngles = sort(thetas);
binAngleEdges = [minAngle; sortedAngles(floor((1:(numAngleBins-1)).*length(sortedAngles)./numAngleBins)); maxAngle]';
sortedRadii = sort(radii);
binRadiusEdges = [0; sortedRadii(floor((1:(numRadialBins-1)).*length(sortedRadii)./numRadialBins)); max(radii)]';
else
% Space the bins equally
binAngleEdges = minAngle:angleStep:maxAngle;
binRadiusEdges = 0:radiusStep:max(radii);
end
[angleHistCounts,binnedAngles] = histc(thetas, binAngleEdges);
[radiiHistCounts,binnedRadii] = histc(radii, binRadiusEdges);
angleBinValues = 1:numAngleBins; % unique(binnedAngles);
radiusBinValues = 1:numRadialBins; % unique(binnedRadii);
if (extraBinForPlotWrap)
valuesForEachBin = zeros(length(angleBinValues) + 1, length(radiusBinValues));
else
valuesForEachBin = zeros(length(angleBinValues), length(radiusBinValues));
end
numberOfSamples = 0;
for aIndex = 1 : length(angleBinValues)
for rIndex = 1 : length(radiusBinValues)
indicesForThisBin = find((binnedAngles == angleBinValues(aIndex)) & (binnedRadii == radiusBinValues(rIndex)));
if (plotMean)
valueForThisBin = mean(valuesToPlot(indicesForThisBin));
else
% Plot a density: compute total then divide by area.
valueForThisBin = sum(valuesToPlot(indicesForThisBin));
areaOfBin = pi .* (binRadiusEdges(rIndex+1).^2 - binRadiusEdges(rIndex).^2) .* ...
mod(abs(binAngleEdges(aIndex+1) - binAngleEdges(aIndex)), 2.*pi) ./ (2.*pi);
valueForThisBin = valueForThisBin ./ areaOfBin;
end
if (length(indicesForThisBin) == 0)
valueForThisBin = 0;
end
% fprintf('Mean value for r=%.1f+,theta=%.3f+ is %.3f (from %d samples)\n', binRadiusEdges(rIndex), ...
% binAngleEdges(aIndex), valueForThisBin, length(indicesForThisBin));
numberOfSamples = numberOfSamples + length(indicesForThisBin);
valuesForEachBin(aIndex, rIndex) = valueForThisBin;
end
end
% Now convert these so we can plot them:
fprintf('Found %d in total in the bins\n', numberOfSamples);
if (extraBinForPlotWrap)
% And add for first angle again to complete the plot
valuesForEachBin(end,:) = valuesForEachBin(1,:);
[THETA,RR] = meshgrid([(binAngleEdges(1:end-1)+binAngleEdges(2:end))./2, (binAngleEdges(1)+binAngleEdges(2))./2], ...
(binRadiusEdges(1:end-1)+binRadiusEdges(2:end))./2);
else
[THETA,RR] = meshgrid([(binAngleEdges(1:end-1)+binAngleEdges(2:end))./2], ...
(binRadiusEdges(1:end-1)+binRadiusEdges(2:end))./2);
end
[A,B] = pol2cart(THETA,RR);
figure();
surf(A,B,valuesForEachBin','edgecolor','none')
xlabel('x [mm]');
ylabel('y [mm]');
colorbar;
view(0,90)
end

View File

@ -0,0 +1,174 @@
function runAnalysis(properties)
% This high-level function generates the local transfer entropy results, first optimising
% parameters (i.e. embedding length and delay, and source-target lag), then
% storing local transfer entropy values for the optimised parameters.
%
% Author: Joseph T. Lizier, 2019
%
% Inputs:
% - properties (required) - object with properties for the calculations,
% with sub-members as specificied in the loadProperties.m file. If not supplied
% the properties are attempted to be loaded from loadProperties.m
%%
%% Java Information Dynamics Toolkit (JIDT)
%% Copyright (C) 2019, Joseph T. Lizier et al.
%%
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
%%
if (nargin < 1)
fprintf('No properties object supplied, attempting to load properties via a loadProperties script ...');
% By default, just try to load properties locally
if (exist('loadProperties') == 2)
% there is a loadProperties script so attempt to run it to load a properties object
loadProperties;
else
% there is not a loadProperties script
error('No properties object supplied, and no loadProperties script found.');
end
else
% A properties argument was supplied
if (ischar(properties))
% We're assuming it was the name of a properties file
if ((length(properties) > 2) && (strcmp(properties(end-1:end), '.m')))
% Remove the '.m':
properties(end-1:end) = [];
end
if (exist(properties) == 2)
% attempt to run the properties .m file: (after making sure the properties variable is cleared; not necessary but is clean)
propertiesFile = properties;
clear properties;
eval(propertiesFile);
else
error('%s is not an .m file we can find that can be used to load a properties object', properties);
end
% else
% We assume it was the properties object.
end
end
% Step 1: Auto-embed if required:
if (isfield(properties, 'kRange') || isfield(properties, 'tauRange'))
% If any one of these two ranges weren't supplied, set the range variables
% to the value of the corresponding non-range variable:
if (~isfield(properties, 'kRange'))
properties.kRange = properties.k;
end
if (~isfield(properties, 'tauRange'))
properties.tauRange = properties.tau;
end
% Also set the lag to 1 as a dummy if we are optimising over that later as well:
if (~isfield(properties, 'lag'))
properties.lag = 1;
end
% Ask generateObservations to only return the target samples for the AIS calculation
properties.destSamplesOnly = true;
% Optimise k and tau
maxAIS = -inf;
maxAISk = properties.kRange(1);
maxAIStau = properties.tauRange(1);
for k = properties.kRange
properties.k = k;
minTau = min(properties.tauRange);
for tau = properties.tauRange
if ((k == 1) && (tau > minTau))
% We only need compute k=1 for a single tau
continue;
end
properties.tau = tau;
% Generate the observations for k,tau:
[D, Dpast] = generateObservations(properties);
if (isempty(D))
% There were no samples found for the given parameters, presumably k etc are too long
continue;
end
% Compute the AIS:
ais = computeAIS(D, Dpast, properties);
if (ais > maxAIS)
maxAIS = ais;
maxAISk = k;
maxAIStau = tau;
end
end
end
% Optimisation is complete:
properties.k = maxAISk;
properties.tau = maxAIStau;
properties.ais = maxAIS;
properties.destSamplesOnly = false;
fprintf('*** Optmised k=%d and tau=%d (giving AIS=%.4f)\n', properties.k, ...
properties.tau, properties.ais);
else
fprintf('*** Hard-coded values for k=%d and tau=%d to be used\n', properties.k, ...
properties.tau);
end
% Step 2: automatically select the correct lag if required:
teNumSurrogates = properties.teNumSurrogates; % Store this for later, turn it off now
properties.teNumSurrogates = 0; % No need to run any surrogates during parameter fitting
if (isfield(properties, 'lagRange'))
% Caller asks us to maximise the TE over a given range:
maxTE = -inf;
maxTElag = properties.lagRange(1);
for lag = properties.lagRange
properties.lag = lag;
% Generate the observations for k,tau,lag:
[D, Dpast, S, ~, maxSourceSamplesForATarget] = generateObservations(properties);
if (isempty(S))
% There were no samples found for the given parameters, presumably k etc are too long
continue;
end
% Check if we're turning on dynamic correlation exclusion:
if (isfield(properties.jidt, 'autoDynamicCorrelationExclusion'))
properties.jidt.dynamicCorrelationExclusion = maxSourceSamplesForATarget;
end
% Compute the TE:
te = computeTE(S, D, Dpast, properties);
if (te > maxTE)
maxTE = te;
maxTElag = lag;
end
end
% Optimisation is complete:
properties.lag = maxTElag;
properties.tranEntropy = maxTE;
fprintf('*** Optmised lag=%d (giving TE=%.4f)\n', properties.lag, ...
properties.tranEntropy);
else
fprintf('*** Hard-coded value for lag=%d to be used\n', properties.lag);
end
% 3. Compute TE with the correct parameters
% Now, once again pre-process the positional data into velocities, this time
% saving them into the results file (by not requesting [S,D,Dpast] outputs):
generateObservations(properties);
% And load these samples in from the saved file:
load(properties.resultsFile);
properties.teNumSurrogates = teNumSurrogates; % Allow surrogates to be computed for this final run with correct parameters
% And compute the TE again for the optimal parameters, this time
% saving the files:
% Turn on dynamic correlation exclusion if required:
if (isfield(properties.jidt, 'autoDynamicCorrelationExclusion'))
if (properties.jidt.autoDynamicCorrelationExclusion)
properties.jidt.dynamicCorrelationExclusion = maxSourceSamplesForATarget; % maxSourceSamplesForATarget was loaded from the results file
else
properties.jidt.dynamicCorrelationExclusion = 0; % no dynamic correlation exclusion
end
end
% Compute TE with no output arguments so that results are saved
computeTE(S, D, Dpast, properties);
end