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="http://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 }