text: """
  Tests for the asym_logit.py file. These tests do not include tests of
  the functions that perform the mathematical calculations necessary to estimate
  the multinomial Asymmetric Logit model.
  """
  import warnings
  import unittest
  from collections import OrderedDict
  from copy import deepcopy
  from functools import partial
  
  import numpy as np
  import numpy.testing as npt
  import pandas as pd
  from scipy.sparse import csr_matrix
  from scipy.sparse import diags
  
  import pylogit.asym_logit as asym
  
  try:
      # in Python 3 range returns an iterator instead of list
      # to maintain backwards compatibility use "old" version of range
      from past.builtins import xrange, range
  except ImportError:
      pass
  
  class GenericTestCase(unittest.TestCase):
      """
      Defines the common setUp method used for the different type of tests.
      """
  
      def setUp(self):
          # The set up being used is one where there are two choice situations,
          # The first having three alternatives, and the second having only two
          # alternatives. There is one generic variable. Two alternative
          # specific constants and all three shape parameters are used.
  
          # Create the betas to be used during the tests
          self.fake_betas = np.array([-0.6])
  
          # Create the fake outside intercepts to be used during the tests
          self.fake_intercepts = np.array([1, 0.5])
  
          # Create names for the intercept parameters
          self.fake_intercept_names = ["ASC 1", "ASC 2"]
  
          # Record the position of the intercept that is not being estimated
          self.fake_intercept_ref_pos = 2
  
          # Create the shape parameters to be used during the tests. Note that
          # these are the reparameterized shape parameters, thus they will be
          # exponentiated in the fit_mle process and various calculations.
          self.fake_shapes = np.array([-1, 1])
  
          # Create names for the intercept parameters
          self.fake_shape_names = ["Shape 1", "Shape 2"]
  
          # Record the position of the shape parameter that is being constrained
          self.fake_shape_ref_pos = 2
  
          # Calculate the 'natural' shape parameters
          self.natural_shapes = asym._convert_eta_to_c(self.fake_shapes,
                                                       self.fake_shape_ref_pos)
  
          # Create an array of all model parameters
          self.fake_all_params = np.concatenate((self.fake_shapes,
                                                 self.fake_intercepts,
                                                 self.fake_betas))
  
          # The mapping between rows and alternatives is given below.
          self.fake_rows_to_alts = csr_matrix(np.array([[1, 0, 0],
                                                        [0, 1, 0],
                                                        [0, 0, 1],
                                                        [1, 0, 0],
                                                        [0, 0, 1]]))
  
          # Create the fake design matrix with columns denoting X
          # The intercepts are not included because they are kept outside the
          # index in the scobit model.
          self.fake_design = np.array([[1],
                                       [2],
                                       [3],
                                       [1.5],
                                       [3.5]])
  
          # Create the index array for this set of choice situations
          self.fake_index = self.fake_design.dot(self.fake_betas)
  
          # Create the needed dataframe for the Asymmetric Logit constructor
          self.fake_df = pd.DataFrame({"obs_id": [1, 1, 1, 2, 2],
                                       "alt_id": [1, 2, 3, 1, 3],
                                       "choice": [0, 1, 0, 0, 1],
                                       "x": self.fake_design[:, 0],
                                       "intercept": [1 for i in range(5)]})
  
          # Record the various column names
          self.alt_id_col = "alt_id"
          self.obs_id_col = "obs_id"
          self.choice_col = "choice"
  
          # Create the index specification  and name dictionaryfor the model
          self.fake_specification = OrderedDict()
          self.fake_names = OrderedDict()
          self.fake_specification["x"] = [[1, 2, 3]]
          self.fake_names["x"] = ["x (generic coefficient)"]
  
          # Bundle args and kwargs used to construct the Asymmetric Logit model.
          self.constructor_args = [self.fake_df,
                                   self.alt_id_col,
                                   self.obs_id_col,
                                   self.choice_col,
                                   self.fake_specification]
  
          # Create a variable for the kwargs being passed to the constructor
          self.constructor_kwargs = {"intercept_ref_pos":
                                     self.fake_intercept_ref_pos,
                                     "shape_ref_pos": self.fake_shape_ref_pos,
                                     "names": self.fake_names,
                                     "intercept_names":
                                     self.fake_intercept_names,
                                     "shape_names": self.fake_shape_names}
  
          # Initialize a basic Asymmetric Logit model whose coefficients will be
          # estimated.
          self.model_obj = asym.MNAL(*self.constructor_args,
                                     **self.constructor_kwargs)
  
          return None
  
  
  # Note that inheritance is used to share the setUp method.
  class ChoiceObjectTests(GenericTestCase):
      """
      Defines the tests for the Asymmetric Logit model's `__init__` function and
      its class methods.
      """
  
      def test_shape_ignore_msg_in_constructor(self):
          """
          Ensures that a UserWarning is raised when the 'shape_ref_pos' keyword
          argument is not an integer. This warns people against expecting all of
          the shape parameters of the Asymmetric Logit model to be identified.
          It also alerts users that they are using a Asymmetric Logit model when
          they might have been expecting to instantiate a different choice model.
          """
          # Create a variable for the kwargs being passed to the constructor
          kwargs = deepcopy(self.constructor_kwargs)
  
          for pos, item in enumerate(['foo', None]):
              kwargs["shape_ref_pos"] = item
  
              # Check that the warning has been created.
              # Test to ensure that the ValueError is raised when using a
              # shape_ref_pos kwarg with an incorrect number of parameters
              self.assertRaises(ValueError, asym.MNAL,
                                *self.constructor_args, **kwargs)
  
          return None
  
      def test_ridge_warning_in_fit_mle(self):
          """
          Ensure that a UserWarning is raised when one passes the ridge keyword
          argument to the `fit_mle` method of an Asymmetric Logit model object.
          """
          # Create a variable for the fit_mle function's kwargs.
          # The print_res = False arguments are to make sure strings aren't
          # printed to the console unnecessarily.
          kwargs = {"ridge": 0.5,
                    "print_res": False,
                    "method": "bfgs"}
  
          # Test to make sure that the ridge warning message is printed when
          # using the ridge keyword argument
          with warnings.catch_warnings(record=True) as w:
              # Use this filter to always trigger the  UserWarnings
              warnings.simplefilter('always', UserWarning)
  
              self.model_obj.fit_mle(self.fake_all_params, **kwargs)
              self.assertGreaterEqual(len(w), 1)
              self.assertIsInstance(w[0].category, type(UserWarning))
              self.assertIn(asym._ridge_warning_msg, str(w[0].message))
  
          return None
  
      def test_init_shapes_length_error_in_fit_mle(self):
          """
          Ensures that ValueError is raised if init_shapes has wrong length.
          """
          # Create a variable for the arguments to the fit_mle function.
          # Note `None` is the argument passed when using the init_shapes,
          # init_intercepts and init_coefs keyword arguments.
          fit_args = [None]
  
          # Create base set of kwargs for fit_mle function
          kwargs = {"init_intercepts": self.fake_intercepts,
                    "init_coefs": self.fake_betas,
                    "print_res": False}
  
          for i in [1, -1]:
              # This will ensure we have too many or too few shape parameters.
              num_shapes = self.fake_rows_to_alts.shape[1] - 1 + i
              kwargs["init_shapes"] = np.arange(num_shapes)
  
              # Test to ensure that the ValueError is raised when using an
              # init_shapes kwarg with an incorrect number of parameters
              self.assertRaises(ValueError, self.model_obj.fit_mle,
                                *fit_args, **kwargs)
  
      def test_init_intercepts_length_error_in_fit_mle(self):
          """
          Ensures that ValueError is raised if init_intercepts has wrong length.
          """
          # Create a variable for the arguments to the fit_mle function.
          # Note `None` is the argument passed when using the init_shapes,
          # init_intercepts and init_coefs keyword arguments.
          fit_args = [None]
  
          # Create base set of kwargs for fit_mle function
          kwargs = {"init_shapes": self.fake_shapes,
                    "init_coefs": self.fake_betas,
                    "print_res": False}
  
          for i in [1, -1]:
              # This will ensure we have too many or too few intercepts
              num_intercepts = self.fake_rows_to_alts.shape[1] - 1 + i
              kwargs["init_intercepts"] = np.arange(num_intercepts)
  
              # Test to ensure that the ValueError when using an init_intercepts
              # kwarg with an incorrect number of parameters
              self.assertRaises(ValueError, self.model_obj.fit_mle,
                                *fit_args, **kwargs)
  
          return None
  
      def test_init_coefs_length_error_in_fit_mle(self):
          """
          Ensures that ValueError is raised if init_coefs has wrong length.
          """
          # Create a variable for the arguments to the fit_mle function.
          # Note `None` is the argument passed when using the init_shapes,
          # init_intercepts and init_coefs keyword arguments.
          fit_args = [None]
  
          # Create base set of kwargs for fit_mle function
          kwargs = {"init_shapes": self.fake_shapes,
                    "init_intercepts": self.fake_intercepts,
                    "print_res": False}
  
          # Note there is only one beta, so we can't go lower than zero betas.
          for i in [1, -1]:
              # This will ensure we have too many or too few intercepts
              num_coefs = self.fake_betas.shape[0] + i
              kwargs["init_coefs"] = np.arange(num_coefs)
  
              # Test to ensure that the ValueError when using an init_intercepts
              # kwarg with an incorrect number of parameters
              self.assertRaises(ValueError, self.model_obj.fit_mle,
                                *fit_args, **kwargs)
  
          return None
  
      def test_insufficient_initial_values_in_fit_mle(self):
          """
          Ensure that value errors are raised if neither init_vals OR
          (init_shapes and init_coefs) are passed.
          """
          # Create a variable for the arguments to the fit_mle function.
          # Note `None` is the argument passed when using the init_shapes,
          # init_intercepts and init_coefs keyword arguments.
          fit_args = [None]
  
          # Create base set of incorrect kwargs for fit_mle function
          kwargs = {"init_shapes": None,
                    "init_intercepts": self.fake_intercepts,
                    "init_coefs": None,
                    "print_res": False}
  
          kwargs_2 = {"init_shapes": self.fake_shapes,
                      "init_intercepts": self.fake_intercepts,
                      "init_coefs": None,
                      "print_res": False}
  
          kwargs_3 = {"init_shapes": None,
                      "init_intercepts": self.fake_intercepts,
                      "init_coefs": self.fake_betas,
                      "print_res": False}
  
          for bad_kwargs in [kwargs, kwargs_2, kwargs_3]:
              # Test to ensure that the ValueError when not passing
              # kwarg with an incorrect number of parameters
              self.assertRaises(ValueError, self.model_obj.fit_mle,
                                *fit_args, **bad_kwargs)
  
          return None
  
      def test_keyword_argument_constructor_in_fit_mle(self):
          """
          Ensures that the init_vals object can be successfully created from the
          various init_shapes, init_intercepts, and init_coefs arguments.
          """
          # Create a variable for the arguments to the fit_mle function.
          # Note `None` is the argument passed when using the init_shapes,
          # init_intercepts and init_coefs keyword arguments.
          fit_args = [None]
  
          # Create base set of incorrect kwargs for fit_mle function (note the
          # ridge is the thing that is incorrect)
          kwargs_1 = {"init_shapes": self.fake_shapes,
                      "init_intercepts": self.fake_intercepts,
                      "init_coefs": self.fake_betas,
                      "ridge": "foo",
                      "print_res": False}
  
          kwargs_2 = {"init_shapes": self.fake_shapes,
                      "init_coefs": self.fake_betas,
                      "ridge": "foo",
                      "print_res": False}
  
          # Test to ensure that the raised ValueError is printed when using
          # either of these two kwargs. This ensures that we were able to
          # create the init_vals object since the ridge error check is after
          # the creation of this argurment.
          for kwargs in [kwargs_1, kwargs_2]:
              self.assertRaisesRegexp(TypeError,
                                      "ridge",
                                      self.model_obj.fit_mle,
                                      *fit_args,
                                      **kwargs)
  
          return None
  
      def test_init_vals_length_error_in_fit_mle(self):
          """
          Ensures that ValueError is raised if init_vals has wrong length.
          """
          # Note there is only one beta, so we can't go lower than zero betas.
          original_intercept_ref_position = self.fake_intercept_ref_pos
          for intercept_ref_position in [None, original_intercept_ref_position]:
              self.model_obj.intercept_ref_position = intercept_ref_position
              for i in [1, -1]:
                  # This will ensure we have too many or too few intercepts
                  num_coefs = self.fake_betas.shape[0] + i
  
                  # Test to ensure that the ValueError when using an
                  # init_intercepts kwarg with an incorrect number of parameters
                  self.assertRaisesRegexp(ValueError,
                                          "dimension",
                                          self.model_obj.fit_mle,
                                          np.arange(num_coefs),
                                          print_res=False)
  
          return None
  
      def test_just_point_kwarg(self):
          # Alias the function being tested
          func = self.model_obj.fit_mle
          # Get the necessary kwargs
          kwargs = {"just_point": True}
          # Get the function results
          func_result = func(self.fake_all_params, **kwargs)
          # Perform the desired tests to make sure we get back a dictionary with
          # an "x" key in it and a value that is a ndarray.
          self.assertIsInstance(func_result, dict)
          self.assertIn("x", func_result)
          self.assertIsInstance(func_result["x"], np.ndarray)
          return None
  
  
  # As before, inheritance is used to share the setUp method.
  class HelperFuncTests(GenericTestCase):
      """
      Defines tests for the 'helper' functions for estimating the Asymmetric
      Logit model.
      """
  
      def test_split_param_vec_with_intercepts(self):
          """
          Ensures that split_param_vec returns (shapes, intercepts, index_coefs)
          when called from within asym_logit.py.
          """
          # Store the results of split_param_vec()
          split_results = asym.split_param_vec(self.fake_all_params,
                                               self.fake_rows_to_alts,
                                               self.fake_design)
          # Check for expected results.
          for item in split_results[1:]:
              self.assertIsInstance(item, np.ndarray)
              self.assertEqual(len(item.shape), 1)
          npt.assert_allclose(split_results[0], self.fake_shapes)
          npt.assert_allclose(split_results[1], self.fake_intercepts)
          npt.assert_allclose(split_results[2], self.fake_betas)
  
          return None
  
      def test_split_param_vec_without_intercepts(self):
          """
          Ensures that split_param_vec returns (shapes, intercepts, index_coefs)
          when called from within asym_logit.py.
          """
          # Store the results of split_param_vec()
          shapes_and_betas = np.concatenate([self.fake_shapes,
                                             self.fake_betas])
          split_results = asym.split_param_vec(shapes_and_betas,
                                               self.fake_rows_to_alts,
                                               self.fake_design)
          # Check for expected results.
          for idx in [0, 2]:
              self.assertIsInstance(split_results[idx], np.ndarray)
              self.assertEqual(len(split_results[idx].shape), 1)
          npt.assert_allclose(split_results[0], self.fake_shapes)
          self.assertIsNone(split_results[1])
          npt.assert_allclose(split_results[2], self.fake_betas)
  
          return None
  
      def test_convert_eta_to_c(self):
          """
          Check general transformation of estimated shape parameters to the
          original parameterization. Check overflow handling.
          """
          # Create a 2d array of shapes, where each row represents another
          # vector of shapes to be transformed and tested
          test_shapes = np.array([[-1, 1],
                                  [800, 1],
                                  [-1, -800],
                                  [0, 0]])
  
          #####
          # Determine the expected results
          #####
          expected_results = np.ones((test_shapes.shape[0], 3))
          expected_results[-1] = 1.0 / 3
          # Explicitly calculate the results for the first row
          vals_1 = np.array([test_shapes[0, 0], test_shapes[0, 1], 0])
          exp_vals_1 = np.exp(vals_1)
          denom_1 = exp_vals_1.sum()
          expected_results[0] = exp_vals_1 / denom_1
          # Explicitly calculate the results for the middle rows, taking care of
          # overflow and underflow.
          vals_2 = np.array([test_shapes[1, 0], test_shapes[1, 1], 0])
          exp_vals_2 = np.exp(vals_2)
          exp_vals_2[0] = asym.max_comp_value
          denom_2 = exp_vals_2.sum()
          expected_results[1] = exp_vals_2 / denom_2
  
          vals_3 = np.array([test_shapes[2, 0], test_shapes[2, 1], 0])
          exp_vals_3 = np.exp(vals_3)
          exp_vals_3[1] = asym.min_comp_value
          denom_3 = exp_vals_3.sum()
          expected_results[2] = exp_vals_3 / denom_3
  
          # Use _convert_eta_to_c and compare the results for 1D inputs
          for i in xrange(test_shapes.shape[0]):
              func_results = asym._convert_eta_to_c(test_shapes[i],
                                                    self.fake_shape_ref_pos)
  
              # Make sure the results are correct
              self.assertIsInstance(func_results, np.ndarray)
              self.assertEqual(len(func_results.shape), 1)
              self.assertEqual(func_results.shape[0], expected_results.shape[1])
              npt.assert_allclose(func_results, expected_results[i])
  
          # Check the 2D inputs. Note that for 2D ndarray, we need the array of
          # shape parameters to be transposed such that we have an array of
          # shape (num_estimated_shapes, num_samples_of_parameters).
          # To adequately compare, we need to transpose expected_results too.
          func_results_2d = asym._convert_eta_to_c(test_shapes.T,
                                                   self.fake_shape_ref_pos)
          # Make sure the results are correct
          self.assertIsInstance(func_results_2d, np.ndarray)
          self.assertEqual(len(func_results_2d.shape), 2)
          self.assertEqual(func_results_2d.shape, expected_results.T.shape)
          npt.assert_allclose(func_results_2d, expected_results.T)
  
          return None
  
      def test_asym_utility_transform(self):
          """
          Ensures that `_asym_utility_transform()` returns correct results
          """
          # Create a set of systematic utilities that will test the function for
          # correct calculations, for proper dealing with overflow, and for
          # proper dealing with underflow.
  
          # The first and third elements tests general calculation.
          # The second element of index_array should lead to the transformation
          # equaling the 'natural' shape parameter for alternative 2.
          # The fourth element should test what happens with underflow and should
          # lead to max_comp_value.
          # The fifth element should test what happens with overflow and should
          # lead to -1.0 * max_comp_value
          index_array = np.array([1, 0, -1, 1e400, -1e400])
  
          # We can use a the following array of the shape parameters to test
          # the underflow capabilities with respect to the shape
          # parameters.
          test_shapes_2 = np.array([-800, 0])
  
          test_shapes_3 = np.array([800, 0])
  
          # Figure out the value of the 'natural' shape parameters
          natural_shapes_2 = asym._convert_eta_to_c(test_shapes_2,
                                                    self.fake_shape_ref_pos)
          natural_shapes_3 = asym._convert_eta_to_c(test_shapes_3,
                                                    self.fake_shape_ref_pos)
  
          # Crerate the array of expected results when using shape parameters
          # of 'normal' magnitudes.
          intercept_1 = self.fake_intercepts[0]
          intercept_2 = self.fake_intercepts[1]
          intercept_3 = 0
  
          result_1 = (intercept_1 +
                      np.log(self.natural_shapes[0]) * (1 - index_array[0]))
  
          result_2 = intercept_2 + np.log(self.natural_shapes[1])
  
          # Note the division by 2 is due to the 'J - 1' term. See the original
          # definition of the transformation.
          result_3 = (intercept_3 +
                      np.log(self.natural_shapes[2]) -
                      np.log((1 - self.natural_shapes[2]) / 2) * index_array[2])
  
          expected_results = np.array([result_1,
                                       result_2,
                                       result_3,
                                       asym.max_comp_value + intercept_1,
                                       - asym.max_comp_value])[:, None]
  
          # Crerate the array of expected results when using shape parameters
          # of 'abnormally' small magnitudes.
          # Note the division by 2 is due to the 'J - 1' term. See the original
          # definition of the transformation.
          result_2_2 = intercept_2 + np.log(natural_shapes_2[1])
          result_3_2 = (intercept_3 +
                        np.log(natural_shapes_2[2]) -
                        np.log((1 - natural_shapes_2[2]) / 2) * index_array[2])
  
          # Note the '0' comes from (1-1) * ln(shape)
          expected_results_2 = np.array([0 + intercept_1,
                                         result_2_2,
                                         result_3_2,
                                         asym.max_comp_value + intercept_1,
                                         -asym.max_comp_value])[:, None]
  
          # Create the array of expected results when using shape parameters
          # of 'abnormally' large magnitudes.
          result_2_3 = intercept_2 + np.log(natural_shapes_3[1])
          result_3_3 = (intercept_3 +
                        np.log(natural_shapes_3[2]) -
                        np.log((1 - natural_shapes_3[2]) / 2) * index_array[2])
  
          expected_results_3 = np.array([0 + intercept_1,
                                         result_2_3,
                                         result_3_3,
                                         0 + intercept_1,
                                         -asym.max_comp_value])[:, None]
  
          #####
          # Perform various rounds of checking
          #####
          # Use the utility transformation function, round_1
          alt_id_vals = self.fake_df[self.alt_id_col].values
          args = [index_array,
                  alt_id_vals,
                  self.fake_rows_to_alts,
                  self.fake_shapes,
                  self.fake_intercepts]
          kwargs = {"intercept_ref_pos": self.fake_intercept_ref_pos,
                    "shape_ref_position": self.fake_shape_ref_pos}
          func_results = asym._asym_utility_transform(*args, **kwargs)
  
          # Use the utility transformation function, round_2
          args[3] = test_shapes_2
          func_results_2 = asym._asym_utility_transform(*args, **kwargs)
  
          # Use the utility transformation function, round_3
          args[3] = test_shapes_3
          func_results_3 = asym._asym_utility_transform(*args, **kwargs)
  
          # Check the correctness of the results
          all_results = [(func_results, expected_results),
                         (func_results_2, expected_results_2),
                         (func_results_3, expected_results_3)]
  
          for pos, (test_results, correct_results) in enumerate(all_results):
              self.assertIsInstance(test_results, np.ndarray)
              self.assertEqual(len(test_results.shape), 2)
              self.assertEqual(test_results.shape[1], correct_results.shape[1])
              self.assertEqual(test_results.shape[0], correct_results.shape[0])
              npt.assert_allclose(test_results, correct_results)
  
          return None
  
      def test_asym_utility_transform_2d(self):
          """
          Ensures that `_asym_utility_transform()` returns correct results when
          called with 2 dimensional systematic utility arrays and
          """
          # Create a set of systematic utilities that will test the function for
          # correct calculations, for proper dealing with overflow, and for
          # proper dealing with underflow.
  
          # The first and third elements tests general calculation.
          # The second element of index_array should lead to the transformation
          # equaling the 'natural' shape parameter for alternative 2.
          # The fourth element should test what happens with underflow and should
          # lead to max_comp_value.
          # The fifth element should test what happens with overflow and should
          # lead to -1.0 * max_comp_value
          index_array = np.array([1, 0, -1, 1e400, -1e400])
          index_array_2d = np.concatenate([index_array[:, None],
                                           index_array[:, None]],
                                          axis=1)
  
          # Create 2d array of shapes
          shapes_2d = np.concatenate([self.fake_shapes[:, None],
                                      self.fake_shapes[:, None]],
                                     axis=1)
  
          # Create 2d array of intercepts
          intercepts_2d = np.concatenate([self.fake_intercepts[:, None],
                                          self.fake_intercepts[:, None]],
                                         axis=1)
  
          # We can use a the following array of the shape parameters to test
          # the underflow capabilities with respect to the shape
          # parameters.
          test_shapes_2 = np.array([-800, 0])
          test_shapes_2_2d = np.concatenate([test_shapes_2[:, None],
                                             test_shapes_2[:, None]],
                                            axis=1)
  
          test_shapes_3 = np.array([800, 0])
          test_shapes_3_2d = np.concatenate([test_shapes_3[:, None],
                                             test_shapes_3[:, None]],
                                            axis=1)
  
          # Figure out the value of the 'natural' shape parameters
          natural_shapes_2 = asym._convert_eta_to_c(test_shapes_2,
                                                    self.fake_shape_ref_pos)
          natural_shapes_3 = asym._convert_eta_to_c(test_shapes_3,
                                                    self.fake_shape_ref_pos)
  
          # Crerate the array of expected results when using shape parameters
          # of 'normal' magnitudes.
          intercept_1 = self.fake_intercepts[0]
          intercept_2 = self.fake_intercepts[1]
          intercept_3 = 0
  
          result_1 = (intercept_1 +
                      np.log(self.natural_shapes[0]) * (1 - index_array[0]))
  
          result_2 = intercept_2 + np.log(self.natural_shapes[1])
  
          # Note the division by 2 is due to the 'J - 1' term. See the original
          # definition of the transformation.
          result_3 = (intercept_3 +
                      np.log(self.natural_shapes[2]) -
                      np.log((1 - self.natural_shapes[2]) / 2) * index_array[2])
  
          expected_results = np.array([result_1,
                                       result_2,
                                       result_3,
                                       asym.max_comp_value + intercept_1,
                                       - asym.max_comp_value])[:, None]
  
          # Crerate the array of expected results when using shape parameters
          # of 'abnormally' small magnitudes.
          # Note the division by 2 is due to the 'J - 1' term. See the original
          # definition of the transformation.
          result_2_2 = intercept_2 + np.log(natural_shapes_2[1])
          result_3_2 = (intercept_3 +
                        np.log(natural_shapes_2[2]) -
                        np.log((1 - natural_shapes_2[2]) / 2) * index_array[2])
  
          # Note the '0' comes from (1-1) * ln(shape)
          expected_results_2 = np.array([0 + intercept_1,
                                         result_2_2,
                                         result_3_2,
                                         asym.max_comp_value + intercept_1,
                                         -asym.max_comp_value])[:, None]
  
          # Create the array of expected results when using shape parameters
          # of 'abnormally' large magnitudes.
          result_2_3 = intercept_2 + np.log(natural_shapes_3[1])
          result_3_3 = (intercept_3 +
                        np.log(natural_shapes_3[2]) -
                        np.log((1 - natural_shapes_3[2]) / 2) * index_array[2])
  
          expected_results_3 = np.array([0 + intercept_1,
                                         result_2_3,
                                         result_3_3,
                                         0 + intercept_1,
                                         -asym.max_comp_value])[:, None]
  
          #####
          # Perform various rounds of checking
          #####
          # Use the utility transformation function, round_1
          alt_id_vals = self.fake_df[self.alt_id_col].values
          args = [index_array_2d,
                  alt_id_vals,
                  self.fake_rows_to_alts,
                  shapes_2d,
                  intercepts_2d]
          kwargs = {"intercept_ref_pos": self.fake_intercept_ref_pos,
                    "shape_ref_position": self.fake_shape_ref_pos}
          func_results = asym._asym_utility_transform(*args, **kwargs)
  
          # Use the utility transformation function, round_2
          args[3] = test_shapes_2_2d
          func_results_2 = asym._asym_utility_transform(*args, **kwargs)
  
          # Use the utility transformation function, round_3
          args[3] = test_shapes_3_2d
          func_results_3 = asym._asym_utility_transform(*args, **kwargs)
  
          # Check the correctness of the results
          all_results = [(func_results, expected_results),
                         (func_results_2, expected_results_2),
                         (func_results_3, expected_results_3)]
  
          for pos, (test_results, correct_results) in enumerate(all_results):
              self.assertIsInstance(test_results, np.ndarray)
              self.assertEqual(len(test_results.shape), 2)
              self.assertEqual(test_results.shape[1], 2)
              self.assertEqual(test_results.shape[0], correct_results.shape[0])
              for col in [0, 1]:
                  npt.assert_allclose(test_results[:, col][:, None],
                                      correct_results)
  
          return None
  
      def test_asym_transform_deriv_v(self):
          """
          Tests basic behavior of the asym_transform_deriv_v. Essentially the
          only things that can go wrong is underflow or overflow from the shape
          parameters going to zero or 1.0.
          """
          # Declare  set of index values to be tested
          test_index = np.array([1, 0, -1, -2, 2])
          # Figure out how many alternatives are there
          num_alts = self.fake_rows_to_alts.shape[1]
          # Test what happens with large shape parameters (that result in
          # 'natural' shape parameters near 1.0)
          large_shapes = np.array([800, 0])
          large_results = np.array([0,
                                    -np.log(asym.min_comp_value),
                                    np.log(num_alts - 1),
                                    asym.max_comp_value,
                                    -np.log(asym.min_comp_value)])
          # Test what happens with large shape parameters (that result in
          # 'natural' shape parameters near 1.0)
          small_shapes = np.array([-800, 0])
          natural_small_shapes = asym._convert_eta_to_c(small_shapes,
                                                        self.fake_shape_ref_pos)
          small_results = np.array([-np.log(natural_small_shapes[0]),
                                    -np.log(natural_small_shapes[1]),
                                    (np.log(num_alts - 1) -
                                     np.log(1 - natural_small_shapes[2])),
                                    (np.log(num_alts - 1) -
                                     np.log(1 - natural_small_shapes[0])),
                                    -np.log(natural_small_shapes[2])])
  
          # Bundle the arguments needed for _asym_transform_deriv_v()
          args = [test_index,
                  self.fake_df[self.alt_id_col].values,
                  self.fake_rows_to_alts,
                  self.fake_shapes]
  
          # Create the needed output array
          num_rows = test_index.shape[0]
          output = diags(np.ones(num_rows), 0, format='csr')
  
          # Validate the results from asym_transform_deriv_v
          for shape_array, results in [(large_shapes, large_results),
                                       (small_shapes, small_results)]:
              # Get the reslts from asym_transform_deriv_v
              args[-1] = shape_array
              kwargs = {"ref_position": self.fake_shape_ref_pos,
                        "output_array": output}
              derivative = asym._asym_transform_deriv_v(*args, **kwargs)
  
              # Ensure the results are as expected
              self.assertIsInstance(derivative, type(output))
              self.assertEqual(len(derivative.shape), 2)
              self.assertEqual(derivative.shape, (num_rows, num_rows))
              npt.assert_allclose(np.diag(derivative.A), results)
  
          return None
  
      def test_asym_transform_deriv_alpha(self):
          """
          Ensures that asym_transform_deriv_alpha returns the `output_array`
          kwarg.
          """
          # Bundle the args for the function being tested
          args = [self.fake_index,
                  self.fake_df[self.alt_id_col].values,
                  self.fake_rows_to_alts,
                  self.fake_intercepts]
  
          # Take the relevant columns of rows_to_alts, as would be done with
          # outside intercepts, or test None for when there are no outside
          # intercepts.
          for test_output in [None, self.fake_rows_to_alts[:, [0, 1]]]:
              kwargs = {"output_array": test_output}
              derivative_results = asym._asym_transform_deriv_alpha(*args,
                                                                    **kwargs)
              if test_output is None:
                  self.assertIsNone(derivative_results)
              else:
                  npt.assert_allclose(test_output.A, derivative_results.A)
  
          return None
  
      def test_calc_deriv_c_with_respect_to_eta(self):
          """
          Ensure the calculation of the derivative of the natural shape
          parameters with respect to the reparameterized shape parameters is
          correct.
          """
          # Specify some shape parameters to test the function
          natural_test_shapes = np.array([0.5, 0.25, 0.25])
          # Specify an alternative to serve as the reference parameter
          ref_position = 2
          # Create an output array that will contain the derivative
          output = np.zeros((natural_test_shapes.shape[0],
                             natural_test_shapes.shape[0] - 1))
  
          # Record the correct derivatives
          interim_results = np.array([[0.5 - 0.25, -0.5 * 0.25, -0.5 * 0.25],
                                      [-0.5 * 0.25, 0.25 - 0.0625, -0.25**2],
                                      [-0.5 * 0.25, -0.25**2, 0.25 - 0.0625]])
          correct_results = interim_results[:, [0, 1]]
  
          # Get the results from the function
          deriv_args = (natural_test_shapes, ref_position)
          kwargs = {'output_array': output}
          function_results = asym._calc_deriv_c_with_respect_to_eta(*deriv_args,
                                                                    **kwargs)
  
          # Ensure that the results are what I expect them to be
          self.assertIsInstance(function_results, np.ndarray)
          self.assertEqual(len(function_results.shape), 2)
          self.assertEqual(function_results.shape, output.shape)
          npt.assert_allclose(function_results, correct_results)
  
          return None
  
      def test_asym_transform_deriv_shape(self):
          """
          Ensures that the asym_transform_deriv_shape() function provides
          correct results, and handles all forms of overflow and underflow
          correctly.
          """
          # Create index values to test the function with.
          test_index = np.array([1, 0, -2, -1e400, 1e400])
  
          # Figure out how many alternatives are there
          num_alts = self.fake_rows_to_alts.shape[1]
  
          # Test what happens with shape parameters of 'normal' magnitude
          regular_shapes = self.fake_shapes
          natural_reg_shapes = self.natural_shapes
  
          result_2 = (natural_reg_shapes[2]**-1 +
                      test_index[2] / (1.0 - natural_reg_shapes[2]))
  
          regular_results = np.array([0,
                                      natural_reg_shapes[1]**-1,
                                      result_2,
                                      -asym.max_comp_value,
                                      -asym.max_comp_value])
  
          # Test what happens with large shape parameters (that result in
          # 'natural' shape parameters near 1.0)
          large_shapes = np.array([800, 0])
          natural_large_shapes = asym._convert_eta_to_c(large_shapes,
                                                        self.fake_shape_ref_pos)
          large_results = np.array([0,
                                    asym.max_comp_value,
                                    asym.max_comp_value,
                                    -asym.max_comp_value,
                                    -asym.max_comp_value])
  
          # Test what happens with large shape parameters (that result in
          # 'natural' shape parameters near 1.0)
          small_index = test_index.copy()
          small_index[0] = 0.5
  
          small_shapes = np.array([-800, 0])
          natural_small_shapes = asym._convert_eta_to_c(small_shapes,
                                                        self.fake_shape_ref_pos)
          result_3_3 = (natural_small_shapes[2]**-1 +
                        test_index[2] / (1 - natural_small_shapes[2]))
          small_results = np.array([asym.max_comp_value,
                                    natural_small_shapes[1]**-1,
                                    result_3_3,
                                    -asym.max_comp_value,
                                    -asym.max_comp_value])
  
          # Bundle the arguments needed for _asym_transform_deriv_shape()
          args = [test_index,
                  self.fake_df[self.alt_id_col].values,
                  self.fake_rows_to_alts,
                  self.fake_shapes]
  
          # Create the needed output array for the overall derivative with
          # respect to the reparameterized shape parameters
          num_rows = test_index.shape[0]
          output = np.matrix(np.zeros((num_rows, num_alts - 1)), dtype=float)
  
          # Create the needed output array for the intermediate derivative of the
          # 'natural' shape parameters with respect to the reparameterized shape
          # parameters.
          deriv_output = np.zeros((num_alts, num_alts - 1), dtype=float)
  
          # Create the array needed for dh_dc
          dh_dc_array = self.fake_rows_to_alts.copy()
  
          # Create a function that will take the reparameterized shapes and the
          # shape reference position and return the derivative of the natural
          # shapes with respect to the reparameterized shapes.
          interim_deriv_func = partial(asym._calc_deriv_c_with_respect_to_eta,
                                       output_array=deriv_output)
  
          # Take note of the kwargs needed for asym_transform_deriv_shape()
          deriv_kwargs = {"ref_position": self.fake_shape_ref_pos,
                          "dh_dc_array": dh_dc_array,
                          "fill_dc_d_eta": interim_deriv_func,
                          "output_array": output}
  
          # Validate the results from asym_transform_deriv_shape
          test_arrays = [(regular_shapes, natural_reg_shapes, regular_results),
                         (large_shapes, natural_large_shapes, large_results),
                         (small_shapes, natural_small_shapes, small_results)]
          for shape_array, natural_shape_array, interim_results in test_arrays:
              if shape_array[0] == small_shapes[0]:
                  args[0] = small_index
  
              # Get the reslts from asym_transform_deriv_shape()
              args[-1] = shape_array
              derivative = asym._asym_transform_deriv_shape(*args,
                                                            **deriv_kwargs)
  
              # Calculate the derivative of the 'natural' shape parameters with
              # espect to the reparameterized shape parameters.
              interim_args = (natural_shape_array, self.fake_shape_ref_pos)
              kwargs = {"output_array": deepcopy(deriv_output)}
              dc_d_eta = asym._calc_deriv_c_with_respect_to_eta(*interim_args,
                                                                **kwargs)
  
              # Place the interim results in the correct shape
              interim_results = interim_results[:, None]
              interim_results = self.fake_rows_to_alts.multiply(interim_results)
  
              # Calculate the correct results
              results = interim_results.A.dot(dc_d_eta)
  
              # Ensure the results are as expected
              self.assertIsInstance(derivative, type(output))
              self.assertEqual(len(derivative.shape), 2)
              self.assertEqual(derivative.shape, (num_rows, num_alts - 1))
              npt.assert_allclose(derivative.A, results)
  
          return None
  