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         }