input: Write a function to implement the `decode_libsvm` function described in the instruction. The function should take `input` (a `Tensor` of type `string`), `num_features` (an `int` that is `>= 1`), `dtype` (an optional `tf.DType` from: `tf.float32, tf.float64, tf.int32, tf.int64`. Defaults to `tf.float32`), `label_dtype` (an optional `tf.DType` from: `tf.float32, tf.float64, tf.int32, tf.int64`. Defaults to `tf.int64`), and `name` (a name for the operation, optional) as arguments. The function should return a tuple of `Tensor` objects `(label, feature_indices, feature_values, feature_shape)`, where `label` is a `Tensor` of type `label_dtype`, `feature_indices` is a `Tensor` of type `int64`, `feature_values` is a `Tensor` of type `dtype`, and `feature_shape` is a `Tensor` of type `int64`.
  
  Here are some test cases:
  
  assert decode_libsvm([b"1 1:3 4:5"], 5, tf.float32, tf.int64, "test") == (tf.Tensor([1], shape=(1,), dtype=tf.int64), tf.Tensor([[0, 1], [0, 4]], shape=(2, 2), dtype=tf.int64), tf.Tensor([3.0, 5.0], shape=(2,), dtype=tf.float32), tf.Tensor([1, 5], shape=(2,), dtype=tf.int64))
  
  assert decode_libsvm([b"3 1:2.5 3:4"], 5, tf.float64, tf.int32, "test") == (tf.Tensor([3], shape=(1,), dtype=tf.int32), tf.Tensor([[0, 1], [0, 3]], shape=(2, 2), dtype=tf.int64), tf.Tensor([2.5, 4.0], shape=(2,), dtype=tf.float64), tf.Tensor([1, 5], shape=(2,), dtype=tf.int64))
  
  assert decode_libsvm([], 5, tf.int32, tf.float64, "test") == (tf.Tensor([], shape=(0,), dtype=tf.float64), tf.Tensor([], shape=(0, 2), dtype=tf.int64), tf.Tensor([], shape=(0,), dtype=tf.int32), tf.Tensor([0, 5], shape=(2,), dtype=tf.int64))
  
  assert decode_libsvm([b"-1 2:1.2 4:3.4"], 5, label_dtype=tf.int64) == (tf.Tensor([-1], shape=(1,), dtype=tf.int64), tf.Tensor([[0, 2], [0, 4]], shape=(2, 2), dtype=tf.int64), tf.Tensor([1.2, 3.4], shape=(2,), dtype=tf.float32), tf.Tensor([1, 5], shape=(2,), dtype=tf.int64))
  
  assert decode_libsvm([b"0 1:1 3:3"], 5, dtype=tf.float64, label_dtype=tf.float64, name="test") == (tf.Tensor([0.0], shape=(1,), dtype=tf.float64), tf.Tensor([[0, 1], [0, 3]], shape=(2, 2), dtype=tf.int64), tf.Tensor([1.0, 3.0], shape=(2,), dtype=tf.float64), tf.Tensor([1, 5], shape=(2,), dtype=tf.int64))
output: def decode_libsvm(input, num_features, dtype=tf.float32, label_dtype=tf.int64, name=None):
      def parse_line(line):
          parts = line.decode().split()
          label = tf.strings.to_number(parts[0], out_type=label_dtype)
          indices = []
          values = []
          for part in parts[1:]:
              idx, val = part.split(':')
              indices.append([0, int(idx) - 1])
              values.append(tf.strings.to_number(val, out_type=dtype))
          return label, indices, values
  
      labels, indices, values = tf.map_fn(
          parse_line, input,
          fn_output_signature=(label_dtype, tf.int64, dtype)
      )
  
      labels = tf.stack(labels)
      indices = tf.ragged.constant(indices, ragged_rank=1).flat_values
      values = tf.stack(values)
  
      feature_shape = tf.shape(input, out_type=tf.int64)
      feature_shape = tf.concat([feature_shape, [num_features]], axis=0)
  
      return labels, indices, values, feature_shape