code: /*******************************************************************************
   * Copyright (c) 2018 Attila Kovacs <attila[AT]sigmyne.com>.
   * All rights reserved. 
   * 
   * This file is part of crush.
   * 
   *     crush 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.
   * 
   *     crush 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 crush.  If not, see <http://www.gnu.org/licenses/>.
   * 
   * Contributors:
   *     Attila Kovacs <attila[AT]sigmyne.com> - initial API and implementation
   ******************************************************************************/
  
  
  package crush;
  
  import java.lang.reflect.*;
  import java.util.*;
  import java.util.stream.Collectors;
  
  import jnum.Copiable;
  import jnum.data.Statistics;
  
  /**
   * A class for groping channels together. For example, all channels read out through the same readout MUX may
   * constitute such a group. Also, pixels in an instrument, with one or more channels associated to them, are 
   * also an example of a <code>ChannelGroup</code>.
   * <p>
   * 
   * Channel groups which exhibit a correlated behavior, such as correlated noise or a common response to an
   * external signal (such as an electronic modulationm, temeprature drifts, or telescope motion), are associated
   * to an appropriate {@link Mode} (such as a {@link Response} or {@link CorrelatedMode}). 
   * <p>
   * 
   * Channel groups are typically created by {@link Instrument#createGroups()}. They can be hard-coded groups,
   * or else specified in the runtime configurations via the <code>group</code> option.
   * <p>
   * 
   * 
   * @see Pixel
   * @see Mode
   * @See ChannelDivision
   * 
   * 
   * @author Attila Kovacs <attila@sigmyne.com>
   *
   * @param <ChannelType>     The generic type of the channels contained in this group.
   * 
   * 
   */
  public class ChannelGroup<ChannelType extends Channel> extends ArrayList<ChannelType> 
  implements Copiable<ChannelGroup<ChannelType>> {
      /**
       * 
       */
      private static final long serialVersionUID = -922794075467674753L;
  
      private String name;
      private int parallelism = 1;
  
      public ChannelGroup(String name) {
          this.name = name;
      }
  
      public ChannelGroup(String name, int size) {
          super(size);
          this.name = name;
      }
  
      public ChannelGroup(String name, Collection<? extends ChannelType> channelList) {
          this(name);
          addAll(channelList);
      }
  
      public void setThreadCount(int threads) { parallelism = threads; }
  
      public int getThreadCount() { return parallelism; }
  
      @Override
      @SuppressWarnings("unchecked")
      public ChannelGroup<ChannelType> copy() {
          ChannelGroup<ChannelType> copy = (ChannelGroup<ChannelType>) clone();
          copy.clear();
          for(int i=0; i<size(); i++) copy.add((ChannelType) get(i).copy());
          return copy;
      }
  
      public String getName() { return name; }
  
      public void setName(String value) { name = value; }
  
      public ChannelGroup<ChannelType> createGroup() {
          // All good channels
          ChannelGroup<ChannelType> channels = new ChannelGroup<>(name, size());
          channels.addAll(this);
          return channels;
      }
  
      public final int add(String spec, ChannelLookup<ChannelType> lookup) {
          return add(spec, lookup, 0);
      }
      
      public int add(String spec, ChannelLookup<ChannelType> lookup, int excludeFlags) {
  
          ChannelType channel = lookup.get(spec);
          
          if(channel != null) {
              if(channel.isUnflagged(excludeFlags)) {
                  add(channel);
                  return 1;
              }
          }
          
          else if(spec.contains("-")) {
              StringTokenizer tokens = new StringTokenizer(spec, "-");
              if(tokens.countTokens() != 2) return 0;
              
              String from = tokens.nextToken();
              String to = tokens.nextToken();
              
              if(!lookup.contains(from)) return 0;
              if(!lookup.contains(to)) return 0;
              
              int fromIndex = lookup.get(from).getFixedIndex();
              int toIndex = lookup.get(to).getFixedIndex();
              
              int n=0;
              
              for(int i=fromIndex; i <= toIndex; i++) {
                  channel = lookup.get(i);
                  if(channel != null) if(channel.isUnflagged(excludeFlags)) {
                      add(channel);
                      n++;
                  }
              }
              
              return n;
          }
          
          return 0;
      }
  
      
      /**
       * Flags channels as dead (with {@link Channel#FLAG_DEAD}) if they have any of the bit-wise flags enabled matching
       * the specified pattern.
       * 
       * @param flagPattern   The bit-wise flag pattern. Channels that have any of the specified flag bits enabled will be marked
       *                      with {@link Channel#FLAG_DDEAD}.
       */
      public void killFlagged(final int flagPattern) {
          parallelStream().filter(x -> x.isFlagged(flagPattern)).forEach(x -> x.flag(Channel.FLAG_DEAD));
      }
  
      /**
       * Removes channels from this group whose flags match any of the bitwise flags in the specified pattern.
       * 
       * 
       * @param discardFlags  The bit-wise flag pattern. Channels that have any of the specified flag bits enabled 
       *                      will be removed from the group.
       * @return              <code>true</code> if matching channels have been found and removed, otherwise <code>false</code>.
       */
      public boolean removeFlagged(int discardFlags) {
          if(parallelStream().noneMatch(c -> c.isFlagged(discardFlags))) return false;
  
          List<ChannelType> keep = stream().filter(c -> c.isUnflagged(discardFlags)).collect(Collectors.toList());
         
          clear(); 
          addAll(keep);
          trimToSize();
          
          return true;
      }
  
      public void order(final Field field) {	
          Comparator<ChannelType> ordering = new Comparator<ChannelType>() {
              @Override
              public int compare(ChannelType c1, ChannelType c2) {
                  try { Double.compare(field.getDouble(c1), field.getDouble(c2)); }
                  catch(IllegalAccessException e) { CRUSH.error(this, e); }
                  return 0;
              }				
          };
  
          Collections.sort(this, ordering);
      }
  
      public double getTypicalGainMagnitude(float[] G, int excludeFlag) {
          final double[] values = new double[size()];
          int n = 0;
          for(int k=size(); --k >= 0; ) if(get(k).isUnflagged(excludeFlag)) if(!Float.isNaN(G[k]))
              values[n++] = Math.log(1.0 + Math.abs(G[k]));
  
          // Use a robust mean (with 10% tails) to calculate the average gain...
          double aveG = Statistics.Inplace.robustMean(values, 0, n, 0.1);
  
          if(Double.isNaN(aveG)) return 1.0;
  
          aveG = Math.exp(aveG) - 1.0;
          return aveG > 0.0 ? aveG : 1.0;
      }
  
  
      public ChannelGroup<ChannelType> discard(int flagPattern) {
          return discard(flagPattern, DISCARD_ANY_FLAG);
      }
  
  
      public ChannelGroup<ChannelType> discard(int flagPattern, int criterion) {
          ArrayList<ChannelType> keep = new ArrayList<>(size());
          
          for(ChannelType channel : this) {     
              switch(criterion) {
              case DISCARD_ANY_FLAG:
                  if(channel.isUnflagged(flagPattern)) keep.add(channel); break; 
              case KEEP_ANY_FLAG:
                  if(channel.isFlagged(flagPattern)) keep.add(channel); break; 
              case DISCARD_MATCH_FLAGS:
                  if((channel.getFlags() & flagPattern) != flagPattern) keep.add(channel); break; 
              case KEEP_MATCH_FLAGS:
                  if((channel.getFlags() & flagPattern) == flagPattern) keep.add(channel); break; 
              }	
          }
          
          if(keep.size() < size()) {
              clear();
              addAll(keep);
              trimToSize();
          }
          return this;
      }
  
      public abstract class Fork<ReturnType> extends CRUSH.Fork<ReturnType> {	
          public Fork() { super(size(), getThreadCount()); }
  
          @Override
          protected final void processIndex(int index) { process(get(index)); }
  
          protected abstract void process(ChannelType channel);
      }
  
  
      public static final int DISCARD_ANY_FLAG = 0;
      public static final int DISCARD_MATCH_FLAGS = 1;
      public static final int KEEP_ANY_FLAG = 2;
      public static final int KEEP_MATCH_FLAGS = 3;
  
  }
  