class_docstr: LogSoftmax activation function
  :math:`\\varphi(\\mathbf{x})_j =
  \\e^{(\mathbf{x}_j - log{\sum_{k=1}^K e^{\mathbf{x}_k})}}
  where :math:`K` is the total number of neurons in the layer. This
  activation function gets applied row-wise.
class_docstr_tok: ["LogSoftmax","activation","function",":","math",":","`","\\\\varphi","(","\\\\mathbf","{","x","}",")","_j","=","\\\\e^","{","(","\\mathbf","{","x","}","_j","-","log","{","\\sum_","{","k=1","}","^K","e^","{","\\mathbf","{","x","}","_k","}",")","}","}","where",":","math",":","`","K","`","is","the","total","number","of","neurons","in","the","layer",".","This","activation","function","gets","applied","row-wise","."]
code: def c_code(self, node, name, inp, out, sub):
      (x,) = inp
      (sm,) = out
      axis = self.axis if self.axis is not None else np.MAXDIMS
      fail = sub["fail"]
  
      return dedent(
          f"""
              PyArrayObject* op[2];
              npy_uint32 op_flags[2];
              npy_uint32 iter_flags;
              NpyIter* iter;
              NpyIter_IterNextFunc* get_next;
              char** data_ptr;
  
              int x_ndim = PyArray_NDIM({x});
              int axis = {axis};
              int iterate_axis = !(axis == NPY_MAXDIMS || x_ndim == 1);
  
              // Validate inputs
              if ((PyArray_TYPE({x}) != NPY_DOUBLE) &&
                  (PyArray_TYPE({x}) != NPY_FLOAT))
              {{
                  PyErr_SetString(PyExc_TypeError, "not a float");
                  {fail}
              }}
  
              if (axis < 0) axis = x_ndim + axis;
              if ((axis < 0) || (iterate_axis && (axis > x_ndim)))
              {{
                  PyErr_SetString(PyExc_ValueError, "invalid axis in LogSoftmax");
                  {fail}
              }}
  
              // Allocate Output Array
              if (({sm}) == NULL || !(PyArray_CompareLists(PyArray_DIMS({sm}), PyArray_DIMS({x}), x_ndim)))
              {{
                  Py_XDECREF({sm});
                  {sm} = (PyArrayObject*)PyArray_SimpleNew(x_ndim, PyArray_DIMS({x}), PyArray_TYPE({x}));
                  if(!{sm}) {{
                      PyErr_SetString(PyExc_MemoryError, "failed to alloc LogSoftmax output");
                      {fail}
                  }}
              }}
  
              // Create numpy iterator
              op[0] = {x};
              op[1] = {sm};
              op_flags[0] = NPY_ITER_READONLY;
              op_flags[1] = NPY_ITER_READWRITE;
              iter_flags = (iterate_axis)? NPY_ITER_MULTI_INDEX : 0;
              iter = NpyIter_MultiNew(
                  2,
                  op,
                  iter_flags,
                  NPY_KEEPORDER,
                  NPY_NO_CASTING,
                  op_flags,
                  NULL
              );
  
              if (iter == NULL)
              {{
                  PyErr_SetString(PyExc_MemoryError, "failed to create LogSoftmax iterator");
                  {fail}
              }}
  
              // LogSoftmax is applied across the entire array
              if (!iterate_axis)
              {{
                  get_next = NpyIter_GetIterNext(iter, NULL);
                  if (get_next == NULL)
                  {{
                      NpyIter_Deallocate(iter);
                      PyErr_SetString(PyExc_RuntimeError, "Failed to obtain LogSoftmax GetIterNext");
                      {fail}
                  }}
                  data_ptr = NpyIter_GetDataPtrArray(iter);
  
                  // Find axis max
                  dtype_{x}* x_ptr = (dtype_{x}*)data_ptr[0];
                  dtype_{x} max = *x_ptr;
                  if (get_next(iter))
                  {{
                      do
                      {{
                          dtype_{x}* x_ptr = (dtype_{x}*)data_ptr[0];
                          max = (*x_ptr > max)? *x_ptr : max;
                      }} while(get_next(iter));
                  }}
  
                  // Reset Iterator
                  if (NpyIter_GotoIterIndex(iter, 0) == NPY_FAIL)
                  {{
                      PyErr_SetString(PyExc_RuntimeError, "Failed to reset LogSoftmax iterator");
                      {fail}
                  }}
  
                  // Compute xdev and sum(exp(xdev))
                  dtype_{sm} sum_exp_xdev = 0.0;
                  do
                  {{
                      dtype_{x}* x_ptr = (dtype_{x}*)data_ptr[0];
                      dtype_{sm}* sm_ptr = (dtype_{sm}*)data_ptr[1];
                      *sm_ptr = (dtype_{sm})((*x_ptr) - max);
                      sum_exp_xdev += exp(*sm_ptr);
                  }} while(get_next(iter));
  
                  // Reset Iterator
                  if (NpyIter_GotoIterIndex(iter, 0) == NPY_FAIL)
                  {{
                      PyErr_SetString(PyExc_RuntimeError, "Failed to reset LogSoftmax iterator");
                      {fail}
                  }}
  
                  // Subtract log(sum(exp(xdev)))
                  dtype_{sm} log_sum_exp_xdev = log(sum_exp_xdev);
                  do
                  {{
                      dtype_{sm}* sm_ptr = (dtype_{sm}*)data_ptr[1];
                      *sm_ptr -= log_sum_exp_xdev;
                  }} while(get_next(iter));
              }}
  
              // LogSoftmax is applied across a specific axis
              else {{
                  // Collect axis strides and remove it from iteration
                  npy_intp axis_size = PyArray_DIM({x}, axis);
                  npy_intp* axis_stride = NpyIter_GetAxisStrideArray(iter, axis);
                  if  (axis_stride == NULL)
                  {{
                      PyErr_SetString(PyExc_RuntimeError, "Failed to obtain LogSoftmax axis strides");
                      {fail}
                  }}
                  npy_intp x_axis_stride = axis_stride[0] / sizeof(dtype_{x});
                  npy_intp sm_axis_stride = axis_stride[1] / sizeof(dtype_{sm});
  
                  if (NpyIter_RemoveAxis(iter, axis) == NPY_FAIL)
                  {{
                      PyErr_SetString(PyExc_RuntimeError, "Failed to remove LogSoftmax axis from iterator");
                      {fail}
                  }}
  
                  // Iterate over remaining axes
                  get_next = NpyIter_GetIterNext(iter, NULL);
                  if (get_next == NULL)
                  {{
                      NpyIter_Deallocate(iter);
                      PyErr_SetString(PyExc_RuntimeError, "Failed to obtain LogSoftmax GetIterNext");
                      {fail}
                  }}
  
                  data_ptr = NpyIter_GetDataPtrArray(iter);
                  do
                  {{
                      dtype_{x}* x_axis = (dtype_{x}*)data_ptr[0];
                      dtype_{sm}* sm_axis = (dtype_{sm}*)data_ptr[1];
  
                      // Find axis max
                      dtype_{x} max = x_axis[0];
                      for (npy_intp i = 1; i < axis_size; i++)
                      {{
                          dtype_{x} x_val = x_axis[i * x_axis_stride];
                          max = (x_val > max)? x_val : max;
                      }}
  
                      // Compute xdev and sum(exp(xdev))
                      dtype_{sm} sum_exp_xdev = 0.0;
                      for (npy_intp i = 0; i < axis_size; i++)
                      {{
                          sm_axis[i * sm_axis_stride] = (dtype_{x})(x_axis[i * x_axis_stride] - max);
                          sum_exp_xdev += exp(sm_axis[i * sm_axis_stride]);
                      }}
  
                      // Subtract log(sum(exp(xdev))
                      dtype_{sm} log_sum_exp_xdev = log(sum_exp_xdev);
                      for (npy_intp i = 0; i < axis_size; i++)
                      {{
                          sm_axis[i * sm_axis_stride] -= log_sum_exp_xdev;
                      }}
  
                  }} while(get_next(iter));
              }}
              NpyIter_Deallocate(iter);
              """
      )
  
imports: from textwrap import dedent
  import numpy
  import scipy
  from pytensor.graph.basic import Apply
  from pytensor.graph.replace import _vectorize_node
  from pytensor.link.c.op import COp
  from pytensor.tensor.basic import as_tensor_variable
  from pytensor.tensor.elemwise import get_normalized_batch_axes
  from pytensor.tensor.math import gamma, gammaln, neg, sum
label_desc: Use image node_id 8 for calling the LogSoftmax obj's underlying member method code with example usage: obj.c_code(node, name, inp, out, sub) and returns: dedent