def set_multiarray_ndshape_range(spec, feature_name, lower_bounds, upper_bounds):
    """
    Annotate an input or output MLMultiArray feature in a neural network spec
    to accommodate a range of shapes.
    This is different from ``update_multiarray_shape_range``, which works with rank 5
    SBCHW mapping.

    :param spec: MLModel
        The MLModel spec containing the feature.

    :param feature_name: str
        The name of the feature for which to add shape range
        information. If the feature is not found in the input or output
        descriptions then an exception is thrown.

    :param lower_bounds: List[int]
        list of integers specifying the lower bounds of each dimension.
        Length must be same as the rank (length of shape) of the ``feature_name``.

    :param upper_bounds: List[int]
        list of integers specifying the upper bounds of each dimension.
        ``-1`` corresponds to unbounded range.
        Length must be same as the rank (length of shape) of the ``feature_name``.


    Examples
    --------
    .. sourcecode:: python

        import coremltools
        from coremltools.models.neural_network import flexible_shape_utils

        spec = coremltools.utils.load_spec("mymodel.mlmodel")
        # say, the default shape of "my_multiarray_featurename" is (2,3)
        flexible_shape_utils.set_multiarray_ndshape_range(
            spec,
            feature_name="my_multiarray_featurename",
            lower_bounds=[1, 2],
            upper_bounds=[10, -1],
        )

    :return:
        None. The spec is updated.
    """
    feature = _get_feature(spec, feature_name)
    _set_multiarray_ndshape_range_for_feature(feature, lower_bounds, upper_bounds)

    # Bump up specification version
    spec.specificationVersion = max(
        _MINIMUM_NDARRAY_SPEC_VERSION, spec.specificationVersion
    )

    Annotate an input or output MLMultiArray feature in a neural network spec
    to accommodate a range of shapes.
    This is different from ``update_multiarray_shape_range``, which works with rank 5
    SBCHW mapping.

    :param spec: MLModel
        The MLModel spec containing the feature.

    :param feature_name: str
        The name of the feature for which to add shape range
        information. If the feature is not found in the input or output
        descriptions then an exception is thrown.

    :param lower_bounds: List[int]
        list of integers specifying the lower bounds of each dimension.
        Length must be same as the rank (length of shape) of the ``feature_name``.

    :param upper_bounds: List[int]
        list of integers specifying the upper bounds of each dimension.
        ``-1`` corresponds to unbounded range.
        Length must be same as the rank (length of shape) of the ``feature_name``.


    Examples
    --------
    .. sourcecode:: python

        import coremltools
        from coremltools.models.neural_network import flexible_shape_utils

        spec = coremltools.utils.load_spec("mymodel.mlmodel")
        # say, the default shape of "my_multiarray_featurename" is (2,3)
        flexible_shape_utils.set_multiarray_ndshape_range(
            spec,
            feature_name="my_multiarray_featurename",
            lower_bounds=[1, 2],
            upper_bounds=[10, -1],
        )

    :return:
        None. The spec is updated.
    
coremltools/models/neural_network/flexible_shape_utils.py