# This file was automatically generated by SWIG (http://www.swig.org).
# Version 3.0.2
#
# Do not make changes to this file unless you know what you are doing--modify
# the SWIG interface file instead.

# Copyright 2014 Alex Kantchelian
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
# http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Author: Alex Kantchelian, 2014
# akant@cs.berkeley.edu

import random
import numpy as np
from scipy import sparse




"""
This module provides a wrapper for the Convex Polytope Machine C++ code.
"""


from sys import version_info
if version_info >= (2,6,0):
    def swig_import_helper():
        from os.path import dirname
        import imp
        fp = None
        try:
            fp, pathname, description = imp.find_module('_cpm', [dirname(__file__)])
        except ImportError:
            import _cpm
            return _cpm
        if fp is not None:
            try:
                _mod = imp.load_module('_cpm', fp, pathname, description)
            finally:
                fp.close()
            return _mod
    _cpm = swig_import_helper()
    del swig_import_helper
else:
    import _cpm
del version_info
from _cpm import *
try:
    _swig_property = property
except NameError:
    pass # Python < 2.2 doesn't have 'property'.
def _swig_setattr_nondynamic(self,class_type,name,value,static=1):
    if (name == "thisown"): return self.this.own(value)
    if (name == "this"):
        if type(value).__name__ == 'SwigPyObject':
            self.__dict__[name] = value
            return
    method = class_type.__swig_setmethods__.get(name,None)
    if method: return method(self,value)
    if (not static):
        self.__dict__[name] = value
    else:
        raise AttributeError("You cannot add attributes to %s" % self)

def _swig_setattr(self,class_type,name,value):
    return _swig_setattr_nondynamic(self,class_type,name,value,0)

def _swig_getattr(self,class_type,name):
    if (name == "thisown"): return self.this.own()
    method = class_type.__swig_getmethods__.get(name,None)
    if method: return method(self)
    raise AttributeError(name)

def _swig_repr(self):
    try: strthis = "proxy of " + self.this.__repr__()
    except: strthis = ""
    return "<%s.%s; %s >" % (self.__class__.__module__, self.__class__.__name__, strthis,)

try:
    _object = object
    _newclass = 1
except AttributeError:
    class _object : pass
    _newclass = 0


def _swig_setattr_nondynamic_method(set):
    def set_attr(self,name,value):
        if (name == "thisown"): return self.this.own(value)
        if hasattr(self,name) or (name == "this"):
            set(self,name,value)
        else:
            raise AttributeError("You cannot add attributes to %s" % self)
    return set_attr





class Dataset(_Dataset):
  def __init__(self, *args):
    """Constructs a labeled dataset object that can be used for CPM training 
    and prediction (labels will be ignored when used for prediction). 
    This always incurs a memory copy (for either dense or sparse matrices) 
    or allocation (when loading from libSVM format file).
    
    Dataset(filename):
      filename: str

      Creates a dataset from a libSVM file format on disk.

    Dataset(X, Y):
      X: 2d float array-like object. Sparse scipy CSR matrices are supported.
      Y: 1d int array-like object
      
      Creates a dataset from instances X (one instance per row) and labels Y.
    """
    if len(args) == 1:
      super(Dataset, self).__init__(*args)

    if len(args) == 2:
      if sparse.isspmatrix_csr(args[0]):
        super(Dataset, self).__init__(args[0].data, args[0].indices, args[0].indptr, args[1])
      else:
        super(Dataset, self).__init__(*args)
    
    if len(args) > 2:
      raise ValueError("Too many arguments.")

  def getLabels(self):
    """Returns a numpy array of labels."""
    return self._getLabels(int(self.getNInstances()))


class CPM(_CPM):
  def __init__(self, k, C=1.0, entropy=0.0, 
              cost_ratio=1.0, outer_label=1, 
              seed=None):
    """Initialize an empty CPM model.
       
       Inputs:
          k: int -- number of sub-classifiers
          C: float -- inverse of L2 regularization factor
          entropy: float -- minimal assignment entropy to maintain
          cost_ratio: float -- in penalty, cost ratio between negative and positive 
            misclassification training errors
          outer_label: int -- outside (positive) class
          seed: (None, int) -- random seed for reproducibility
    """
    if seed is None:
      seed = int(random.getrandbits(32))

    super(CPM, self).__init__(k, outer_label, 1.0/C, entropy, cost_ratio, seed)

  def fit(self, trainset, iterations=-1, reshuffle=True, verbose=False):
    """Trains a model via SGD.
       
       Inputs:
          trainset: Dataset
          iterations: int -- number of SGD steps. If < 0, will be set to 10 * training set size.
          reshuffle: bool -- reshuffle trainingset between each epoch
          verbose: bool -- print training statistics on stdout
    """
    if iterations < 0:
      iterations = 10 * trainset.getNInstances()
    super(CPM, self).fit(trainset, iterations, reshuffle, verbose)

  def predict(self, testset):
    """Performs inference.
       Input:
          testset: Dataset

       Outputs:
          scores: 1d float array of model scores
          assignments: 1d int array of active sub-classifiers per instance
    """
    return super(CPM, self).predict(testset, int(testset.getNInstances()), int(testset.getNInstances()))


def parallelFitPredict(trainset, testset, parameters):
  """Trains and tests len(parameters) models on trainset and testset respectively.
  This will launch exactly len(parameters) threads, use at your own risk.

  Inputs:
    trainset: Dataset - the learning dataset
    testset: Dataset - the testing dataset
    parameters: list of dict objects. Each dict object can contain the
      following string keys defining the parameters of the run. See CPM class. 
      Default values are provided for some keys.
        k
        outer_label (1)
        iterations
        C (1)
        entropy (0) 
        cost_ratio (1)
        reshuffle: (True)

  Outputs:
    S: len(parameters) x testset.getCounts() float array of scores
    A: len(parameters) x testset.getCounts() int array of assignments
  """
  configs = []
  for params in parameters:
    configs.append(_CPMConfig(params.get('outer_label', 1), params['k'], 1.0/params.get('C', 1),
                             params.get('entropy', 0), params.get('cost_ratio', 1), 
                             params['iterations'], params.get('reshuffle', True)))
    
  S, A = _parallelEval(trainset, testset, configs, 
                       int(len(parameters)*testset.getNInstances()),
                       int(len(parameters)*testset.getNInstances()))
  S.resize((len(parameters), testset.getNInstances()))
  A.resize((len(parameters), testset.getNInstances()))
  
  return S, A



