001package com.astrolabsoftware.FinkBrowser.Januser;
002
003import com.Lomikel.Utils.LomikelException;
004import com.astrolabsoftware.FinkBrowser.HBaser.Clusteriser.ClusterFinder;
005
006// Java
007import java.util.Arrays;
008import java.util.Set;
009import java.util.TreeSet;
010import java.util.Map;
011import java.util.TreeMap;
012import java.util.List;
013import java.util.ArrayList;
014import java.io.IOException;
015
016// Log4J
017import org.apache.logging.log4j.Logger;
018import org.apache.logging.log4j.LogManager;
019
020/** <code>FeaturesClassifier</code> classifies sources according to
021  * HBase <tt>lc_features_*</tt> field.
022  * <em>flavor</em> points to resource carrying model. 
023  * @opt attributes
024  * @opt operations
025  * @opt types
026  * @opt visibility
027  * @author <a href="mailto:Julius.Hrivnac@cern.ch">J.Hrivnac</a> */
028// BUG: jd should be String or long
029public class FeaturesClassifier extends ZTFClassifier {
030
031  @Override
032  public void classify(FinkGremlinRecipies recipies,
033                       String              oid) throws LomikelException {
034    String jd;
035    String cl;
036    Map<String, String> value;
037    String[] featuresS;
038    double[] featuresD;
039    String fg;
040    String fr;
041    Map<String, Set<String>> allInstances; // cl -> [jd]
042    Map<String, Double>      allWeights;   // jd -> w
043    Set<String> jds;
044    String key;
045    Map<String, Map<String, String>> alerts = recipies.fhclient().scan(null,
046                                                                       "key:key:" + oid + ":prefix",
047                                                                       "i:jd,d:lc_features_g,d:lc_features_r",
048                                                                       0,
049                                                                       0,
050                                                                       false,
051                                                                       false);
052    allInstances = new TreeMap<>();
053    allWeights   = new TreeMap<>();
054    // get all alerts (jd) and their classses
055    boolean isClassified = false;
056    for (Map.Entry<String, Map<String, String>> entry : alerts.entrySet()) {
057      value = entry.getValue();
058      jd = value.get("i:jd");
059      if (value.containsKey("d:lc_features_g") &&
060          value.containsKey("d:lc_features_r")) {
061        fg = value.get("d:lc_features_g").replaceFirst("\\[", "").replaceAll("]$", "");
062        fr = value.get("d:lc_features_r").replaceFirst("\\[", "").replaceAll("]$", "");
063        // BUG: some models replace by mean
064        featuresS = (fg + "," + fr).replaceAll("null", "0.0").
065                                    replaceAll("NaN",  "0.0").
066                                    split(",");
067        featuresD = Arrays.stream(featuresS).
068                           mapToDouble(Double::parseDouble).
069                           toArray();
070        cl = String.valueOf(finder().transformAndPredict(featuresD));                  
071        if (!cl.equals("-1")) {
072          if (allInstances.containsKey(cl)) {
073            jds = allInstances.get(cl);
074            jds.add(jd);
075            }
076          else {
077            jds = new TreeSet<String>();
078            jds.add(jd);
079            allInstances.put(cl, jds);
080            }
081          allWeights.put(jd, 1.0);
082          }
083        isClassified = true;
084        }
085      else {
086        //log.warn("Alert " + entry.getKey() + " has no features");
087        }
088      }
089    // rearrange instances and weights and register
090    double weight;
091    double totalWeight;
092    double w;
093    totalWeight = 0;
094    List<String> instancesL;
095    List<Double> weightsL;
096    for (Map.Entry<String, Set<String>> cls : allInstances.entrySet()) {
097      for (String instance : cls.getValue()) {
098        totalWeight += allWeights.get(instance);
099        }
100      }
101    for (Map.Entry<String, Set<String>> cls : allInstances.entrySet()) {
102      key = "FC-" + cls.getKey();
103      instancesL = new ArrayList<String>(cls.getValue());
104      weightsL   = new ArrayList<Double>();
105      w = 0;
106      for (String instance : instancesL) {
107        weightsL.add(allWeights.get(instance));
108        w += allWeights.get(instance);
109        }
110      weight = w / totalWeight;
111      recipies.registerOCol(this, key, oid, weight, instancesL, weightsL);
112      }
113    if (!isClassified) {
114      log.warn("Source " + oid + " cannot be classified because his alerts have no LC features");
115      }
116    }
117    
118  /** Give {@link ClusterFinder} to current database. Singleton.
119    * @return The corresponding {@link ClusterFinder}. 
120    * @throws LomikelExceltion If {@link ClusterFinder} cannot be created. */
121  private ClusterFinder finder() throws LomikelException {
122    if (_finder == null) {
123      try {
124         ClassLoader classLoader = getClass().getClassLoader();
125        _finder = new ClusterFinder(classLoader.getResource("Clusters/ZTF/" + flavor() + "/scaler_params.json"),
126                                    classLoader.getResource("Clusters/ZTF/" + flavor() + "/pca_params.json"),
127                                    classLoader.getResource("Clusters/ZTF/" + flavor() + "/cluster_centers.json"));
128        }                                                   
129      catch (IOException | IllegalArgumentException e) {
130        throw new LomikelException("Cannot create Cluster Finder for " + flavor(), e);
131        }
132      }
133    return _finder;
134    }
135  
136  private ClusterFinder _finder;
137
138  /** Logging . */
139  private static Logger log = LogManager.getLogger(FeaturesClassifier.class);
140  
141  }
142           
143