001 package com.croftsoft.apps.cyborg; 002 003 import java.util.*; 004 import javax.swing.event.*; 005 006 import org.apache.commons.math.stat.descriptive.DescriptiveStatistics; 007 008 import com.croftsoft.core.math.MathConstants; 009 import com.croftsoft.core.math.MathLib; 010 import com.croftsoft.core.util.loop.*; 011 012 /********************************************************************* 013 * Maintains state. 014 * 015 * @version 016 * $Id: CyborgModelImpl.java,v 1.34 2008/04/19 21:30:58 croft Exp $ 017 * @since 018 * 2005-03-16 019 * @author 020 * <a href="https://www.croftsoft.com/">David Wallace Croft</a> 021 *********************************************************************/ 022 023 public final class CyborgModelImpl 024 implements CyborgModel 025 ////////////////////////////////////////////////////////////////////// 026 ////////////////////////////////////////////////////////////////////// 027 { 028 029 private final Random random; 030 031 private final Set<ChangeListener> changeListenerSet; 032 033 private final DescriptiveStatistics descriptiveStatistics; 034 035 private final NanoTimeLoopGovernor nanoTimeLoopGovernor; 036 037 // 038 039 private double 040 aimX, 041 aimY, 042 alpha, 043 max = CyborgConfig.DEFAULT_MAX, 044 offset, 045 x, 046 y, 047 targetCenterX, 048 targetCenterY, 049 targetRadius; 050 051 private String transform; 052 053 // 054 055 private boolean [ ] [ ] spikeRasters; 056 057 private long 058 currentTime, 059 lastOffTargetTime, 060 lastUpdateTime, 061 startTime; 062 063 private boolean paused; 064 065 private boolean 066 animate, 067 forceLength, 068 realTime; 069 070 ////////////////////////////////////////////////////////////////////// 071 ////////////////////////////////////////////////////////////////////// 072 073 public CyborgModelImpl ( ) 074 ////////////////////////////////////////////////////////////////////// 075 { 076 spikeRasters = new boolean [ 4 ] [ 200 ]; 077 078 random = new Random ( ); 079 080 targetRadius = 0.1; 081 082 transform = CyborgConfig.JOYSTICK_TRANSFORM_OPTION_LINEAR; 083 084 changeListenerSet = new HashSet<ChangeListener> ( ); 085 086 descriptiveStatistics = DescriptiveStatistics.newInstance ( ); 087 088 nanoTimeLoopGovernor = new NanoTimeLoopGovernor ( ); 089 090 setRealTime ( true ); 091 092 animate = true; 093 094 forceLength = true; 095 096 reset ( ); 097 } 098 099 ////////////////////////////////////////////////////////////////////// 100 ////////////////////////////////////////////////////////////////////// 101 102 public void reset ( ) 103 ////////////////////////////////////////////////////////////////////// 104 { 105 descriptiveStatistics.clear ( ); 106 107 x = y = aimX = aimY = 0; 108 109 targetCenterX = 2.0 * random.nextDouble ( ) - 1.0; 110 111 targetCenterY = 2.0 * random.nextDouble ( ) - 1.0; 112 113 startTime = currentTime; // System.nanoTime ( ); 114 115 lastOffTargetTime = startTime; 116 } 117 118 ////////////////////////////////////////////////////////////////////// 119 // accessor methods 120 ////////////////////////////////////////////////////////////////////// 121 122 public double getAimX ( ) { return aimX; } 123 124 public double getAimY ( ) { return aimY; } 125 126 public double getAlpha ( ) { return alpha; } 127 128 public boolean getAnimate ( ) { return animate; } 129 130 public boolean getForceLength ( ) { return forceLength; } 131 132 public LoopGovernor getLoopGovernor ( ) 133 { return nanoTimeLoopGovernor; } 134 135 public double getMax ( ) { return max; } 136 137 public double getOffset ( ) { return offset; } 138 139 public boolean getRealTime ( ) { return realTime; } 140 141 public boolean [ ] [ ] getSpikeRasters ( ) { return spikeRasters; } 142 143 public double getX ( ) { return x; } 144 145 public double getY ( ) { return y; } 146 147 public double getTargetCenterX ( ) { return targetCenterX; } 148 149 public double getTargetCenterY ( ) { return targetCenterY; } 150 151 public double getTargetRadius ( ) { return targetRadius; } 152 153 public String getTransform ( ) { return transform; } 154 155 ////////////////////////////////////////////////////////////////////// 156 // mutator methods 157 ////////////////////////////////////////////////////////////////////// 158 159 public void setAimX ( double aimX ) { this.aimX = aimX; } 160 161 public void setAimY ( double aimY ) { this.aimY = aimY; } 162 163 public void setAlpha ( double alpha ) 164 ////////////////////////////////////////////////////////////////////// 165 { 166 final double gain = 1 / ( 1 - alpha ); 167 168 CyborgConfig.INSTANCE.getLog ( ).record ( 169 String.format ( 170 "alpha = %1$1.2f (gain = %2$1.3f)", 171 new Double ( alpha ), 172 new Double ( gain ) ) ); 173 174 this.alpha = alpha; 175 176 reset ( ); 177 178 broadcast ( ); 179 } 180 181 public void setAnimate ( boolean animate ) 182 ////////////////////////////////////////////////////////////////////// 183 { 184 this.animate = animate; 185 186 CyborgConfig.INSTANCE.getLog ( ).record ( "Animate: " + animate ); 187 188 broadcast ( ); 189 } 190 191 public void setForceLength ( boolean forceLength ) 192 ////////////////////////////////////////////////////////////////////// 193 { 194 this.forceLength = forceLength; 195 196 CyborgConfig.INSTANCE.getLog ( ).record ( 197 "Force-length: " + forceLength ); 198 199 broadcast ( ); 200 } 201 202 public void setMax ( double max ) { this.max = max; } 203 204 public void setOffset ( double offset ) 205 ////////////////////////////////////////////////////////////////////// 206 { 207 this.offset = offset; 208 209 CyborgConfig.INSTANCE.getLog ( ).record ( "offset = " + offset ); 210 211 broadcast ( ); 212 } 213 214 public void setRealTime ( boolean realTime ) 215 ////////////////////////////////////////////////////////////////////// 216 { 217 this.realTime = realTime; 218 219 CyborgConfig.INSTANCE.getLog ( ).record ( 220 "Real-time: " + realTime ); 221 222 if ( realTime ) 223 { 224 nanoTimeLoopGovernor.setFrequency ( CyborgConfig.FRAME_RATE ); 225 } 226 else 227 { 228 nanoTimeLoopGovernor.setPeriodNanos ( 0L ); 229 } 230 231 broadcast ( ); 232 } 233 234 public void setTransform ( String transform ) 235 ////////////////////////////////////////////////////////////////////// 236 { 237 this.transform = transform; 238 239 CyborgConfig.INSTANCE.getLog ( ).record ( 240 "Transform: " + transform ); 241 242 reset ( ); 243 244 broadcast ( ); 245 } 246 247 ////////////////////////////////////////////////////////////////////// 248 ////////////////////////////////////////////////////////////////////// 249 250 /********************************************************************* 251 * Transforms from [-1 to +1] to [0 to +1]. 252 *********************************************************************/ 253 public double transform ( double control ) 254 ////////////////////////////////////////////////////////////////////// 255 { 256 control -= offset; 257 258 double value; 259 260 final double gain = 1.0 / ( 1.0 - alpha ); 261 262 // transforms from [-1 to +1] to [0 to +1] 263 final double transformed = ( control + 1.0 ) / 2.0; 264 265 if ( transform.equals ( 266 CyborgConfig.JOYSTICK_TRANSFORM_OPTION_CUMULATIVE ) ) 267 { 268 value = MathLib.cumulative ( transformed, gain ) 269 / MathLib.cumulative ( 1, gain ); 270 } 271 else if ( transform.equals ( 272 CyborgConfig.JOYSTICK_TRANSFORM_OPTION_EXPONENTIAL ) ) 273 { 274 value = ( Math.exp ( gain * transformed ) - 1 ) 275 / ( Math.exp ( gain ) - 1 ); 276 } 277 else if ( transform.equals ( 278 CyborgConfig.JOYSTICK_TRANSFORM_OPTION_LINEAR ) ) 279 { 280 value = ( gain * control + 1 ) / 2; 281 } 282 else if ( transform.equals ( 283 CyborgConfig.JOYSTICK_TRANSFORM_OPTION_LOGARITHMIC ) ) 284 { 285 value = Math.log ( gain * ( ( control + 1 ) / 2 + 1 ) ) 286 / Math.log ( gain * 2.0 ); 287 } 288 else if ( transform.equals ( 289 CyborgConfig.JOYSTICK_TRANSFORM_OPTION_SIGMOIDAL ) ) 290 { 291 final double min = MathLib.sigmoid ( gain * -1 ); 292 293 final double max = MathLib.sigmoid ( gain * 1 ); 294 295 value = ( MathLib.sigmoid ( gain * control ) - min ) 296 / ( max - min ); 297 } 298 else 299 { 300 throw new IllegalStateException ( 301 "unknown transform: " + transform ); 302 } 303 304 if ( value > 1.0 ) 305 { 306 value = 1.0; 307 } 308 else if ( value < 0.0 ) 309 { 310 value = 0.0; 311 } 312 313 return value; 314 } 315 316 public void addChangeListener ( ChangeListener changeListener ) 317 ////////////////////////////////////////////////////////////////////// 318 { 319 changeListenerSet.add ( changeListener ); 320 } 321 322 ////////////////////////////////////////////////////////////////////// 323 ////////////////////////////////////////////////////////////////////// 324 325 public void setPaused ( boolean paused ) 326 ////////////////////////////////////////////////////////////////////// 327 { 328 this.paused = paused; 329 } 330 331 public void update ( ) 332 ////////////////////////////////////////////////////////////////////// 333 { 334 if ( paused ) 335 { 336 return; 337 } 338 339 //long currentTime = System.nanoTime ( ); 340 341 currentTime += ( MathConstants.NANOSECONDS_PER_SECOND / 200 ); 342 343 /* 344 double deltaTime = ( currentTime - lastUpdateTime ) 345 * MathConstants.SECONDS_PER_NANOSECOND; 346 347 lastUpdateTime = currentTime; 348 */ 349 double deltaTime = 1 / 200.0; 350 351 double probability = max * deltaTime; 352 353 for ( int i = 0; i < spikeRasters.length; i++ ) 354 { 355 boolean [ ] spikeRaster = spikeRasters [ i ]; 356 357 for ( int j = spikeRaster.length - 1; j > 0; j-- ) 358 { 359 spikeRaster [ j ] = spikeRaster [ j - 1 ]; 360 } 361 362 double r = random.nextDouble ( ); 363 364 switch ( i ) 365 { 366 case 0: 367 368 spikeRaster [ 0 ] 369 = r <= probability * transform ( -aimY ); 370 371 if ( spikeRaster [ 0 ] ) 372 { 373 y -= ( 374 ( forceLength ? ( ( 1.0 + y ) ) : 1 ) 375 * deltaTime * CyborgConfig.DELTA ); 376 377 if ( y < -1.0 ) 378 { 379 y = -1.0; 380 } 381 } 382 383 break; 384 385 case 1: 386 387 spikeRaster [ 0 ] 388 = r <= probability * transform ( aimY ); 389 390 if ( spikeRaster [ 0 ] ) 391 { 392 y += ( 393 ( forceLength ? ( ( 1.0 - y ) ) : 1 ) 394 * deltaTime * CyborgConfig.DELTA ); 395 396 if ( y > 1.0 ) 397 { 398 y = 1.0; 399 } 400 } 401 402 break; 403 404 case 2: 405 406 spikeRaster [ 0 ] 407 = r <= probability * transform ( -aimX ); 408 409 if ( spikeRaster [ 0 ] ) 410 { 411 x -= ( 412 ( forceLength ? ( ( 1.0 + x ) ) : 1 ) 413 * deltaTime * CyborgConfig.DELTA ); 414 415 if ( x < -1.0 ) 416 { 417 x = -1.0; 418 } 419 } 420 421 break; 422 423 case 3: 424 425 spikeRaster [ 0 ] 426 = r <= probability * transform ( aimX ); 427 428 if ( spikeRaster [ 0 ] ) 429 { 430 x += ( 431 ( forceLength ? ( ( 1.0 - x ) ) : 1 ) 432 * deltaTime * CyborgConfig.DELTA ); 433 434 if ( x > 1.0 ) 435 { 436 x = 1.0; 437 } 438 } 439 440 break; 441 } 442 } 443 444 double distance = Math.sqrt ( 445 Math.pow ( x - targetCenterX, 2.0 ) 446 + Math.pow ( y - targetCenterY, 2.0 ) ); 447 448 if ( distance < targetRadius ) 449 { 450 long onTargetTime = currentTime - lastOffTargetTime; 451 452 if ( onTargetTime >= 3 * MathConstants.NANOSECONDS_PER_SECOND ) 453 { 454 lastOffTargetTime = currentTime; 455 456 double acquisitionTime = ( currentTime - startTime ) 457 * MathConstants.SECONDS_PER_NANOSECOND; 458 459 descriptiveStatistics.addValue ( acquisitionTime ); 460 461 long realCurrentTime = System.nanoTime ( ); 462 463 long sampleSize = descriptiveStatistics.getN ( ); 464 465 boolean doAnimation 466 = animate 467 | realCurrentTime >= lastUpdateTime 468 + 10 * MathConstants.NANOSECONDS_PER_SECOND 469 | ( sampleSize % ( int ) Math.pow ( 10.0, 470 ( int ) Math.log10 ( sampleSize ) ) ) == 0; 471 472 if ( doAnimation ) 473 { 474 double variance = descriptiveStatistics.getVariance ( ); 475 476 double standardError = Math.sqrt ( variance / sampleSize ); 477 478 CyborgConfig.INSTANCE.getLog ( ).record ( 479 String.format ( 480 "X=%1$1.3f N=%2$d M=%3$1.3f SD=%4$1.3f SE=%5$1.3f", 481 new Double ( acquisitionTime ), 482 new Long ( sampleSize ), 483 new Double ( descriptiveStatistics.getMean ( ) ), 484 new Double ( 485 descriptiveStatistics.getStandardDeviation ( ) ), 486 new Double ( standardError ) ) ); 487 488 lastUpdateTime = realCurrentTime; 489 } 490 491 targetCenterX = 2.0 * random.nextDouble ( ) - 1.0; 492 493 targetCenterY = 2.0 * random.nextDouble ( ) - 1.0; 494 495 startTime = currentTime; 496 } 497 } 498 else 499 { 500 lastOffTargetTime = currentTime; 501 } 502 } 503 504 private void broadcast ( ) 505 ////////////////////////////////////////////////////////////////////// 506 { 507 for ( ChangeListener changeListener : changeListenerSet ) 508 { 509 changeListener.stateChanged ( new ChangeEvent ( this ) ); 510 } 511 } 512 513 ////////////////////////////////////////////////////////////////////// 514 ////////////////////////////////////////////////////////////////////// 515 }