001 package com.croftsoft.apps.cyborg; 002 003 import java.io.*; 004 import java.util.*; 005 006 import org.apache.commons.math.MathException; 007 import org.apache.commons.math.distribution.DistributionFactory; 008 import org.apache.commons.math.distribution.NormalDistribution; 009 import org.apache.commons.math.stat.descriptive.DescriptiveStatistics; 010 011 /********************************************************************* 012 * Surrogate directional activity. 013 * 014 * Translated from SurrogateDirectionalActivityV2.xls (2004 October) 015 * by Lawrence J. Cauller, Ph.D., which was donated to the 016 * Public Domain by Dr. Cauller on 2004-12-01. 017 * 018 * @version 019 * 2004-12-23 020 * @since 021 * 2004-10-19 022 * @author 023 * <a href="https://www.croftsoft.com/">David Wallace Croft</a> 024 *********************************************************************/ 025 026 public final class Activity 027 ////////////////////////////////////////////////////////////////////// 028 ////////////////////////////////////////////////////////////////////// 029 { 030 031 private static final int DIRECTIONS = 8; 032 033 private static final int NEURONS = 24; 034 035 private static final int TRIALS = 100; 036 037 // 038 039 private static final double RATE_SPONTANEOUS = 10.0; 040 041 private static final double RATE_MIN = 5.0; 042 043 private static final double RATE_MAX = 50.0; 044 045 private static final double RATE_DEVIATION = 10.0; 046 047 // 048 049 /** Random number generator. */ 050 private static final Random random = new Random ( ); 051 052 /** Used to create normal distributions for adding noise. */ 053 private static final DistributionFactory distributionFactory 054 = DistributionFactory.newInstance ( ); 055 056 // 057 058 private static final String OUTPUT_FILENAME = "out.txt"; 059 060 ////////////////////////////////////////////////////////////////////// 061 ////////////////////////////////////////////////////////////////////// 062 063 public static void main ( String [ ] args ) 064 throws Exception 065 ////////////////////////////////////////////////////////////////////// 066 { 067 // Write the output to a file using PrintStream printf(). 068 069 PrintStream printStream = new PrintStream ( 070 new FileOutputStream ( OUTPUT_FILENAME ) ); 071 072 // Assume we know the preferred stimulus direction for each of the 073 // neurons that we are recording from. 074 075 double [ ] preferredDirections = new double [ NEURONS ]; 076 077 for ( int unit = 0; unit < NEURONS; unit++ ) 078 { 079 // Assume that the neurons we chose have evenly distributed 080 // preferred stimulus directions 081 082 preferredDirections [ unit ] 083 = ( 2.0 * Math.PI * ( unit ) ) / NEURONS; 084 } 085 086 printTemplateActivations ( printStream, preferredDirections ); 087 088 runTrials ( printStream, preferredDirections ); 089 } 090 091 /********************************************************************* 092 * Prints the template activations with one standard dev of noise. 093 *********************************************************************/ 094 public static void printTemplateActivations ( 095 PrintStream printStream, 096 double [ ] preferredDirections ) 097 throws Exception 098 ////////////////////////////////////////////////////////////////////// 099 { 100 double [ ] templateActivations = new double [ NEURONS ]; 101 102 double [ ] addDeviations = new double [ NEURONS ]; 103 104 double [ ] subDeviations = new double [ NEURONS ]; 105 106 printStream.println ( "unit preferred template addDev subDev" ); 107 108 for ( int unit = 0; unit < NEURONS; unit++ ) 109 { 110 templateActivations [ unit ] = computeNoiselessActivation ( 111 0.0, preferredDirections [ unit ] ); 112 113 addDeviations [ unit ] 114 = templateActivations [ unit ] + RATE_DEVIATION; 115 116 subDeviations [ unit ] 117 = templateActivations [ unit ] - RATE_DEVIATION; 118 119 subDeviations [ unit ] = Math.ceil ( subDeviations [ unit ] ); 120 121 if ( subDeviations [ unit ] <= 0.0 ) 122 { 123 subDeviations [ unit ] = 0.0; 124 } 125 126 printStream.printf ( 127 "%1$02d %2$03.0f %3$06.3f %4$06.3f %5$06.3f%n", 128 new Object [ ] { 129 new Integer ( unit ), 130 new Double ( Math.toDegrees ( 131 preferredDirections [ unit ] ) ), 132 new Double ( templateActivations [ unit ] ), 133 new Double ( addDeviations [ unit ] ), 134 new Double ( subDeviations [ unit ] ) } ); 135 } 136 } 137 138 /********************************************************************* 139 * Runs the trials, gathers statistics, and prints the results. 140 *********************************************************************/ 141 public static void runTrials ( 142 PrintStream printStream, 143 double [ ] preferredDirections ) 144 throws Exception 145 ////////////////////////////////////////////////////////////////////// 146 { 147 // initialize statistics objects 148 149 DescriptiveStatistics [ ] descriptiveStatisticsArray 150 = new DescriptiveStatistics [ DIRECTIONS ]; 151 152 DescriptiveStatistics [ ] [ ] 153 averagePopulationVectorsDescriptiveStatistics 154 = new DescriptiveStatistics [ DIRECTIONS ] [ NEURONS ]; 155 156 for ( int i = 0; i < DIRECTIONS; i++ ) 157 { 158 descriptiveStatisticsArray [ i ] 159 = DescriptiveStatistics.newInstance ( ); 160 161 for ( int j = 0; j < NEURONS; j++ ) 162 { 163 averagePopulationVectorsDescriptiveStatistics [ i ] [ j ] 164 = DescriptiveStatistics.newInstance ( ); 165 } 166 } 167 168 DescriptiveStatistics grandDescriptiveStatistics 169 = DescriptiveStatistics.newInstance ( ); 170 171 // run the trial data 172 173 printStream.println ( "\n" + "trial actual predicted error" ); 174 175 for ( int trial = 0; trial < TRIALS; trial++ ) 176 { 177 int directionIndex = ( trial * DIRECTIONS ) / TRIALS; 178 179 double actualDirection 180 = directionIndex * 2.0 * Math.PI / DIRECTIONS; 181 182 double [ ] noisyActivations = new double [ NEURONS ]; 183 184 for ( int unit = 0; unit < NEURONS; unit++ ) 185 { 186 // record the activity from a single neuron for the given trial 187 188 noisyActivations [ unit ] = generateNoisyActivation ( 189 preferredDirections [ unit ], 190 actualDirection ); 191 192 // record the statistical data for this direction and this unit 193 194 averagePopulationVectorsDescriptiveStatistics 195 [ directionIndex ] [ unit ].addValue ( 196 noisyActivations [ unit ] ); 197 } 198 199 // Knowing only the preferred directions for each neuron and their 200 // current activations, predict the direction for this trial. 201 202 double predictedDirection = predictDirection ( 203 preferredDirections, 204 noisyActivations ); 205 206 // Compute the error between predicted and actual direction 207 208 double error = Math.abs ( Math.min ( 209 predictedDirection - actualDirection, 210 actualDirection + 2.0 * Math.PI - predictedDirection ) ); 211 212 // How variable is the error for the trials in a given direction? 213 214 descriptiveStatisticsArray [ directionIndex ].addValue ( error ); 215 216 // How variable is the error from trial to trial? 217 218 grandDescriptiveStatistics.addValue ( error ); 219 220 // trial actual predicted error 221 222 printStream.printf ( 223 "%1$02d %2$03.0f %3$07.3f %4$07.3f%n", 224 new Object [ ] { 225 new Integer ( trial ), 226 new Double ( Math.toDegrees ( actualDirection ) ), 227 new Double ( Math.toDegrees ( predictedDirection ) ), 228 new Double ( Math.toDegrees ( error ) ) } ); 229 } 230 231 printStream.println ( "\n" + 232 "direction / mean error / error std / mean-error / mean+error" ); 233 234 for ( int i = 0; i < DIRECTIONS; i++ ) 235 { 236 double direction = i * 2.0 * Math.PI / DIRECTIONS; 237 238 double meanError = descriptiveStatisticsArray [ i ].getMean ( ); 239 240 double errorStd 241 = descriptiveStatisticsArray [ i ].getStandardDeviation ( ); 242 243 // the mean error and standard deviation in error for a direction 244 245 printStream.printf ( 246 "%1$03.0f %2$06.3f %3$06.3f %4$06.3f %5$06.3f%n", 247 new Double ( Math.toDegrees ( direction ) ), 248 new Double ( Math.toDegrees ( meanError ) ), 249 new Double ( Math.toDegrees ( errorStd ) ), 250 new Double ( Math.toDegrees ( meanError - errorStd ) ), 251 new Double ( Math.toDegrees ( meanError + errorStd ) ) ); 252 } 253 254 // the mean error and standard deviation in error for all trials 255 256 printStream.println ( "\n" + "grand mean error: " 257 + Math.toDegrees ( grandDescriptiveStatistics.getMean ( ) ) ); 258 259 printStream.println ( "\n" + "grand deviation: " 260 + Math.toDegrees ( 261 grandDescriptiveStatistics.getStandardDeviation ( ) ) ); 262 263 // print the mean activity from each neuron for each direction 264 265 printStream.println ( "\n" + "direction preferred average" ); 266 267 for ( int i = 0; i < DIRECTIONS; i++ ) 268 { 269 for ( int j = 0; j < NEURONS; j++ ) 270 { 271 printStream.printf ( 272 "%1$03.0f %2$03.0f %3$03.0f%n", 273 new Double [ ] { 274 new Double ( 275 Math.toDegrees ( i * 2.0 * Math.PI / DIRECTIONS ) ), 276 new Double ( 277 Math.toDegrees ( j * 2.0 * Math.PI / NEURONS ) ), 278 new Double ( 279 Math.ceil ( 280 averagePopulationVectorsDescriptiveStatistics 281 [ i ] [ j ] 282 .getMean ( ) ) ) } ); 283 } 284 } 285 } 286 287 /********************************************************************* 288 * Generates activation for a neuron without random noise. 289 *********************************************************************/ 290 public static double computeNoiselessActivation ( 291 double preferredDirection, 292 double actualDirection ) 293 ////////////////////////////////////////////////////////////////////// 294 { 295 double angleFromPreferred 296 = preferredDirection - actualDirection; 297 298 if ( angleFromPreferred < 0.0 ) 299 { 300 angleFromPreferred += 2.0 * Math.PI; 301 } 302 303 double computedActivation; 304 305 if ( ( angleFromPreferred <= Math.PI / 2 ) 306 || ( angleFromPreferred >= 3 * Math.PI / 2 ) ) 307 { 308 computedActivation 309 = RATE_SPONTANEOUS 310 + Math.cos ( angleFromPreferred ) 311 * ( RATE_MAX - RATE_SPONTANEOUS ); 312 } 313 else 314 { 315 computedActivation 316 = RATE_SPONTANEOUS 317 + Math.cos ( angleFromPreferred ) 318 * ( RATE_SPONTANEOUS - RATE_MIN ); 319 } 320 321 return computedActivation; 322 } 323 324 /********************************************************************* 325 * Generates activation for a neuron with random noise. 326 *********************************************************************/ 327 public static double generateNoisyActivation ( 328 double preferredDirection, 329 double actualDirection ) 330 throws Exception 331 ////////////////////////////////////////////////////////////////////// 332 { 333 double noiselessActivation = computeNoiselessActivation ( 334 preferredDirection, 335 actualDirection ); 336 337 double probability = random.nextDouble ( ); 338 339 double noisyActivation = Math.ceil ( 340 normInv ( 341 probability, 342 noiselessActivation, 343 RATE_DEVIATION ) ); 344 345 if ( noisyActivation <= 0.0 ) 346 { 347 noisyActivation = 0.0; 348 } 349 350 return noisyActivation; 351 } 352 353 public static double normInv ( 354 double probability, 355 double mean, 356 double standardDeviation ) 357 throws MathException 358 ////////////////////////////////////////////////////////////////////// 359 { 360 NormalDistribution normalDistribution 361 = distributionFactory.createNormalDistribution ( 362 mean, standardDeviation ); 363 364 return 365 normalDistribution.inverseCumulativeProbability ( probability ); 366 } 367 368 /********************************************************************* 369 * Predicts the direction for this trial. 370 * Knowing only the preferred directions for each neuron and their 371 * current activations, predict the direction for this trial. 372 * This method is the heart of the algorithm. 373 *********************************************************************/ 374 public static double predictDirection ( 375 double [ ] preferredDirections, 376 double [ ] noisyActivations ) 377 ////////////////////////////////////////////////////////////////////// 378 { 379 double sumCosComponents = 0.0; 380 381 double sumSinComponents = 0.0; 382 383 for ( int unit = 0; unit < NEURONS; unit++ ) 384 { 385 // weight the activation of each neuron by their cartesian 386 // components along their preferred directions 387 388 double cosComponent 389 = Math.cos ( preferredDirections [ unit ] ) 390 * noisyActivations [ unit ]; 391 392 double sinComponent 393 = Math.sin ( preferredDirections [ unit ] ) 394 * noisyActivations [ unit ]; 395 396 // sum the weighted contributions from each neuron for this trial 397 398 sumCosComponents += cosComponent; 399 400 sumSinComponents += sinComponent; 401 } 402 403 // Here is the predicted direction. 404 405 double predictedDirection = Math.atan2 ( 406 sumSinComponents, 407 sumCosComponents ); 408 409 // return a non-negative angle for the answer 410 411 if ( predictedDirection < 0.0 ) 412 { 413 predictedDirection += 2.0 * Math.PI; 414 } 415 416 // Note the arctan adjustment is not necessary since 417 // the atan2(y,x) function was used. 418 419 return predictedDirection; 420 } 421 422 ////////////////////////////////////////////////////////////////////// 423 ////////////////////////////////////////////////////////////////////// 424 }