// assume numFns x numDerivVars x numDerivVars
// returns false if conversion failed
bool NRELPythonApplicInterface::
python_convert(PyObject *pyma, RealSymMatrixArray &rma)
{
#ifdef DAKOTA_PYTHON_NUMPY
  if (userNumpyFlag) {
    // cannot recurse in this case as we now have a symmetric matrix 
    // (clearer this way anyway)
    if (!PyArray_Check(pyma) || PyArray_NDIM(pyma) != 3 || 
	PyArray_DIM(pyma,0) != numFns || PyArray_DIM(pyma,1) != numDerivVars ||
	PyArray_DIM(pyma,2) != numDerivVars ) {
      Cerr << "Python numpy array not 3D of size " << numFns << "x"
	   << numDerivVars << "x" << numDerivVars << "." << std::endl;
      return(false);
    }
    PyArrayObject *pao = (PyArrayObject *) pyma;
    for (int i=0; i<numFns; ++i)
      for (int j=0; j<numDerivVars; ++j)
	for (int k=0; k<=j; ++k)
	  rma[i](j,k) = *(double *)(pao->data + i*(pao->strides[0]) + 
				    j*(pao->strides[1]) +
				    k*(pao->strides[2]));
  }
  else
#endif
  {
    PyObject *val;
    if (!PyList_Check(pyma) || PyList_Size(pyma) != numFns) {
      Cerr << "Python matrix array must have " << numFns << " rows."
	   << std::endl;
      return(false);
    }
    for (int i=0; i<numFns; ++i) {
      val = PyList_GetItem(pyma, i);
      if (PyList_Check(val)) {
	if (!python_convert(val, rma[i]))
	  return(false);
      }
      else {
	Cerr << "Each row of Python matrix must be a list." << std::endl;
	Py_DECREF(val);
	return(false);
      }
    }
  }
  return(true);
}
// assume we're converting numFns x numDerivVars to numDerivVars x
// numFns (gradients) returns false if conversion failed
bool NRELPythonApplicInterface::python_convert(PyObject *pym, RealMatrix &rm)
{
#ifdef DAKOTA_PYTHON_NUMPY
  if (userNumpyFlag) {
    if (!PyArray_Check(pym) || PyArray_NDIM(pym) != 2 || 
	PyArray_DIM(pym,0) != numFns  ||  PyArray_DIM(pym,1) != numDerivVars) {
      Cerr << "Python numpy array not 2D of size " << numFns << "x"
	   << numDerivVars << "." << std::endl;
      return(false);
    }
    PyArrayObject *pao = (PyArrayObject *) pym;
    for (int i=0; i<numFns; ++i)
      for (int j=0; j<numDerivVars; ++j)
	rm(j,i) = *(double *)(pao->data + i*(pao->strides[0]) + 
			      j*(pao->strides[1]));
  }
  else
#endif
  {
    PyObject *val;
    if (!PyList_Check(pym) || PyList_Size(pym) != numFns) {
      Cerr << "Python matrix must have " << numFns << "rows." << std::endl;
      return(false);
    }
    for (int i=0; i<numFns; ++i) {
      val = PyList_GetItem(pym, i);
      if (PyList_Check(val)) {
	// use the helper to convert this column of the gradients
	if (!python_convert(val, rm[i], numDerivVars))
	  return(false);
      }
      else {
	Cerr << "Each row of Python matrix must be a list." << std::endl;
	Py_DECREF(val);
	return(false);
      }
    }
  }
  return(true);
}
  int NRELPythonApplicInterface::derived_map_ac(const Dakota::String& ac_name)
  {
    printf ("entered python:derived_map_ac\n");

  // probably need to convert all of the following with SWIG or Boost!!
  // (there is minimal error checking for now)
  // need to cleanup ref counts on Python objects
  int fail_code = 0;

  // probably want to load the modules and functions at construction time, incl.
  // validation and store the objects for later, but need to resolve use of
  // analysisDriverIndex

  // for now we presume a single analysis component containing module:function
  const std::string& an_comp = analysisComponents[analysisDriverIndex][0];
  size_t pos = an_comp.find(":");
  std::string module_name = an_comp.substr(0,pos);
  std::string function_name = an_comp.substr(pos+1);

  printf ("importing the module %s\n", module_name.c_str());
  // import the module and function and test for callable
  PyObject *pModule = PyImport_Import(PyString_FromString(module_name.c_str()));
  if (pModule == NULL) {
    Cerr << "Error (Direct:Python): Failure importing module " << module_name 
	 << ".\n                       Consider setting PYTHONPATH."
	 << std::endl;
    abort_handler(-1);
  }
  printf ("imported the module\n");

  // Microsoft compiler chokes on this:
  //  char fn[function_name.size()+1];
  char *fn = new char[function_name.size()+1];
  strcpy(fn, function_name.c_str());
  PyObject *pFunc = PyObject_GetAttrString(pModule, fn);
  if (!pFunc || !PyCallable_Check(pFunc)) {
    Cerr << "Error (Direct:Python): Function " << function_name  << "not found "
	 << "or not callable" << std::endl;
    abort_handler(-1);
  }
  delete fn;

  // must use empty tuple here to pass to function taking only kwargs
  PyObject *pArgs = PyTuple_New(0);
  PyObject *pDict = PyDict_New();

  // convert DAKOTA data types to Python objects (lists and/or numpy arrays)
  PyObject *cv, *cv_labels, *div, *div_labels, *drv, *drv_labels,
    *av, *av_labels, *asv, *dvv;
  python_convert(xC, &cv);
  python_convert(xCLabels, &cv_labels);
  python_convert_int(xDI, xDI.length(), &div);
  python_convert(xDILabels, &div_labels);
  python_convert(xDR, &drv);
  python_convert(xDRLabels, &drv_labels);
  python_convert(xC, xDI, xDR, &av);
  python_convert(xCLabels, xDILabels, xDRLabels, &av_labels);
  python_convert_int(directFnASV, directFnASV.size(), &asv);
  python_convert_int(directFnDVV, directFnASV.size(), &dvv);
  // TO DO: analysis components

  // assemble everything into a dictionary to pass to user function
  // this should eat references to the objects declared above
  PyDict_SetItem(pDict, PyString_FromString("variables"), 
		 PyInt_FromLong((long) numVars));
  PyDict_SetItem(pDict, PyString_FromString("functions"), 
		 PyInt_FromLong((long) numFns)); 
  PyDict_SetItem(pDict, PyString_FromString("cv"), cv);
  PyDict_SetItem(pDict, PyString_FromString("cv_labels"), cv_labels);
  PyDict_SetItem(pDict, PyString_FromString("div"), div);
  PyDict_SetItem(pDict, PyString_FromString("div_labels"), div_labels);
  PyDict_SetItem(pDict, PyString_FromString("drv"), drv);
  PyDict_SetItem(pDict, PyString_FromString("drv_labels"), drv_labels);
  PyDict_SetItem(pDict, PyString_FromString("av"), av);
  PyDict_SetItem(pDict, PyString_FromString("av_labels"), av_labels);
  PyDict_SetItem(pDict, PyString_FromString("asv"), asv);
  PyDict_SetItem(pDict, PyString_FromString("dvv"), dvv);
  // Does not appear to exist in Windows version of Dakota, and I don't recall using it:
  //  PyDict_SetItem(pDict, PyString_FromString("fnEvalId"), 
  //		 PyInt_FromLong((long) fnEvalId)); 

  ///////
  ///PyDict_SetItem(pDict, PyString_FromString("data"), gData); 
  printf ("I have data at : %lx,   NOW WHAT do I do!?\n",  pUserData); 
  ///////
  bp::object * tmp2 = NULL;
  if (pUserData != NULL)
    {
      printf ("Setting raw PyObject in dict\n");
      tmp2 = (bp::object*)pUserData;
      PyDict_SetItem(pDict, PyString_FromString("user_data"), 
		     tmp2->ptr());
    }
  else
    printf("no data\n");


  // perform analysis
  if (outputLevel > NORMAL_OUTPUT)
    Cout << "Info (Direct:Python): Calling function " << function_name 
	 << " in module " << module_name << "." << std::endl;
  PyObject *retVal = PyObject_Call(pFunc, pArgs, pDict);
  Py_DECREF(pDict);
  Py_DECREF(pArgs);    
  Py_DECREF(pFunc);
  Py_DECREF(pModule);
  if (!retVal) {
    // TODO: better error reporting from Python
    Cerr << "Error (Direct:Python): Unknown error evaluating python "
	 << "function." << std::endl;
    abort_handler(-1);
  }
      
  bool fn_flag = false;
  for (int i=0; i<numFns; ++i)
    if (directFnASV[i] & 1) {
      fn_flag = true;
      break;
    }

  // process return type as dictionary, else assume list of functions only
  if (PyDict_Check(retVal)) {
    // or the user may return a dictionary containing entires fns, fnGrads,
    // fnHessians, fnLabels, failure (int)
    // fnGrads, e.g. is a list of lists of doubles
    // this is where Boost or SWIG could really help
    // making a lot of assumptions on types being returned
    PyObject *obj;
    if (fn_flag) {
      if ( !(obj = PyDict_GetItemString(retVal, "fns")) ) {
	Cerr << "Python dictionary must contain list 'fns'" << std::endl;
	Py_DECREF(retVal);
	abort_handler(-1);
      }
      if (!python_convert(obj, fnVals, numFns)) {
	Py_DECREF(retVal);
	abort_handler(-1);
      }
    }
    if (gradFlag) {
      if ( !(obj = PyDict_GetItemString(retVal, "fnGrads")) ) {
	Cerr << "Python dictionary must contain list 'fnGrads'" << std::endl;
	Py_DECREF(retVal);
	abort_handler(-1);
      }
      if (!python_convert(obj, fnGrads)) {
	Py_DECREF(retVal);
	abort_handler(-1);
      }
    }
    if (hessFlag) {
      if ( !(obj = PyDict_GetItemString(retVal, "fnHessians")) ) {
	Cerr << "Python dictionary must contain list 'fnHessians'" << std::endl;
	Py_DECREF(retVal);
	abort_handler(-1);
      }
      if (!python_convert(obj, fnHessians)){
	Py_DECREF(retVal);
	abort_handler(-1);
      }
    }
    // optional returns
    if (obj = PyDict_GetItemString(retVal, "failure"))
      fail_code = PyInt_AsLong(obj);

    if (obj = PyDict_GetItemString(retVal, "fnLabels")) {
      if (!PyList_Check(obj) || PyList_Size(obj) != numFns) {
	Cerr << "'fnLabels' must be list of length numFns." << std::endl;
	Py_DECREF(retVal);
	abort_handler(-1);
      }
      for (int i=0; i<numFns; ++i)
	fnLabels[i] = PyString_AsString(PyList_GetItem(obj, i));
    }
  }
  else {
    // asssume list/numpy array containing only functions
    if (fn_flag)
      python_convert(retVal, fnVals, numFns);
  }
  Py_DECREF(retVal);

  return(fail_code);    
  }
int PythonInterface::python_run(const String& ac_name)
{
  // probably need to convert all of the following with SWIG or Boost!!
  // (there is minimal error checking for now)
  // need to cleanup ref counts on Python objects
  int fail_code = 0;

  // probably want to load the modules and functions at construction time, incl.
  // validation and store the objects for later, but need to resolve use of
  // analysisDriverIndex

  // must use empty tuple here to pass to function taking only kwargs
  PyObject *pArgs = PyTuple_New(0);
  PyObject *pDict = PyDict_New();

  // convert DAKOTA data types to Python objects (lists and/or numpy arrays)
  PyObject *cv, *cv_labels, *div, *div_labels, *drv, *drv_labels,
    *av, *av_labels, *asv, *dvv, *an_comps;
  python_convert(xC, &cv);
  python_convert_strlist(xCLabels, &cv_labels);
  python_convert_int(xDI, xDI.length(), &div);
  python_convert_strlist(xDILabels, &div_labels);
  python_convert(xDR, &drv);
  python_convert_strlist(xDRLabels, &drv_labels);
  python_convert(xC, xDI, xDR, &av);
  python_convert(xCLabels, xDILabels, xDRLabels, &av_labels);
  python_convert_int(directFnASV, directFnASV.size(), &asv);
  python_convert_int(directFnDVV, directFnDVV.size(), &dvv);
  // send analysis components, or an empty list
  if (analysisComponents.size() > 0)
    python_convert_strlist(analysisComponents[analysisDriverIndex], &an_comps);
  else
    an_comps = PyList_New(0);

  // assemble everything into a dictionary to pass to user function
  // this should eat references to the objects declared above
  PyDict_SetItem(pDict, PyString_FromString("variables"), 
		 PyInt_FromLong((long) numVars));
  PyDict_SetItem(pDict, PyString_FromString("functions"), 
		 PyInt_FromLong((long) numFns)); 
  PyDict_SetItem(pDict, PyString_FromString("cv"), cv);
  PyDict_SetItem(pDict, PyString_FromString("cv_labels"), cv_labels);
  PyDict_SetItem(pDict, PyString_FromString("div"), div);
  PyDict_SetItem(pDict, PyString_FromString("div_labels"), div_labels);
  PyDict_SetItem(pDict, PyString_FromString("drv"), drv);
  PyDict_SetItem(pDict, PyString_FromString("drv_labels"), drv_labels);
  PyDict_SetItem(pDict, PyString_FromString("av"), av);
  PyDict_SetItem(pDict, PyString_FromString("av_labels"), av_labels);
  PyDict_SetItem(pDict, PyString_FromString("asv"), asv);
  PyDict_SetItem(pDict, PyString_FromString("dvv"), dvv);
  PyDict_SetItem(pDict, PyString_FromString("analysis_components"), an_comps);
  PyDict_SetItem(pDict, PyString_FromString("currEvalId"), 
		 PyInt_FromLong((long) currEvalId));


  // The active analysis_driver is passed in ac_name (in form
  // module:function); could make module optional.  We pass any
  // analysis components as string arguments to the Python function.
  size_t pos = ac_name.find(":");
  std::string module_name = ac_name.substr(0,pos);
  std::string function_name = ac_name.substr(pos+1);
  if (module_name.size() == 0 || function_name.size() == 0) {
    Cerr << "\nError: invalid Python analysis_driver '" << ac_name
	 << "'\n       Should have form 'module:function'." << std::endl;
    abort_handler(-1);
  }

  // import the module and function and test for callable
  PyObject *pModule = PyImport_Import(PyString_FromString(module_name.c_str()));
  if (pModule == NULL) {
    Cerr << "Error (PythonInterface): Failure importing module '" 
	 << module_name  << "'.\n                         Consider setting "
	 << "PYTHONPATH." << std::endl;
    abort_handler(-1);
  }

  PyObject *pFunc = PyObject_GetAttrString(pModule, function_name.c_str());
  if (!pFunc || !PyCallable_Check(pFunc)) {
    Cerr << "Error (PythonInterface): Function '" << function_name  
	 << "' not found or not callable" << std::endl;
    abort_handler(-1);
  }

  // perform analysis
  if (outputLevel > NORMAL_OUTPUT)
    Cout << "Info (PythonInterface): Calling function " << function_name 
	 << " in module " << module_name << "." << std::endl;
  PyObject *retVal = PyObject_Call(pFunc, pArgs, pDict);
  if (!retVal) {
    // TODO: better error reporting from Python
    Cerr << "Error (PythonInterface): Unknown error evaluating python "
	 << "function." << std::endl;
    abort_handler(-1);
  }

  Py_DECREF(pDict);
  Py_DECREF(pArgs);    
  Py_DECREF(pFunc);
  Py_DECREF(pModule);

  // process the return data

  bool fn_flag = false;
  for (int i=0; i<numFns; ++i)
    if (directFnASV[i] & 1) {
      fn_flag = true;
      break;
    }

  // process return type as dictionary, else assume list of functions only
  if (PyDict_Check(retVal)) {
    // or the user may return a dictionary containing entires fns, fnGrads,
    // fnHessians, fnLabels, failure (int)
    // fnGrads, e.g. is a list of lists of doubles
    // this is where Boost or SWIG could really help
    // making a lot of assumptions on types being returned
    PyObject *obj;
    if (fn_flag) {
      if ( !(obj = PyDict_GetItemString(retVal, "fns")) ) {
	Cerr << "Python dictionary must contain list 'fns'" << std::endl;
	Py_DECREF(retVal);
	abort_handler(-1);
      }
      if (!python_convert(obj, fnVals, numFns)) {
	Py_DECREF(retVal);
	abort_handler(-1);
      }
    }
    if (gradFlag) {
      if ( !(obj = PyDict_GetItemString(retVal, "fnGrads")) ) {
	Cerr << "Python dictionary must contain list 'fnGrads'" << std::endl;
	Py_DECREF(retVal);
	abort_handler(-1);
      }
      if (!python_convert(obj, fnGrads)) {
	Py_DECREF(retVal);
	abort_handler(-1);
      }
    }
    if (hessFlag) {
      if ( !(obj = PyDict_GetItemString(retVal, "fnHessians")) ) {
	Cerr << "Python dictionary must contain list 'fnHessians'" << std::endl;
	Py_DECREF(retVal);
	abort_handler(-1);
      }
      if (!python_convert(obj, fnHessians)){
	Py_DECREF(retVal);
	abort_handler(-1);
      }
    }
    // optional returns
    if (obj = PyDict_GetItemString(retVal, "failure"))
      fail_code = PyInt_AsLong(obj);

    if (obj = PyDict_GetItemString(retVal, "fnLabels")) {
      if (!PyList_Check(obj) || PyList_Size(obj) != numFns) {
	Cerr << "'fnLabels' must be list of length numFns." << std::endl;
	Py_DECREF(retVal);
	abort_handler(-1);
      }
      for (int i=0; i<numFns; ++i)
	fnLabels[i] = PyString_AsString(PyList_GetItem(obj, i));
    }
  }
  else {
    // asssume list/numpy array containing only functions
    if (fn_flag)
      python_convert(retVal, fnVals, numFns);
  }
  Py_DECREF(retVal);

  return(fail_code);
}