Restrict post navigation to current sub menu

As @Ravs mentions the third parameter in next_post_link()/previous_post_link() should be a boolean value indicating whether to restrict the link to a post in the same category. By default this is false. If set to true, it will choose the next (or previous) post which is in the same category as the current post. This includes all parent & child categories.

The functions accept a third parameter: an array of category IDs to be excluded – but as pointed out in the comments, you cannot exclude categories to which the post belongs if you set the ‘in same category’ argument to true.

So one solution is to set ‘in same category’ to false, and exclude all terms which are not associated with a post, or which are, but have a child who is also associated with a post. (This is fairly messy, you can use the SQL filters if you prefer)

The following returns terms that are associated with a post, and who have child terms also associated with the post:

/**
 * Returns a term IDs of terms that are associated with a post, and who have
 * child terms also associated with the post.
 *
 * Please note, 'parent' is a slight misnomer. If you have category structure: 
 *    Cat A > Sub-Cat B > Sub-Sub-Cat C
 * and a post belongs to A and B, but not C. 'B' is not considered a parent term
 * for this post.
 *
 * @link https://wordpress.stackexchange.com/questions/101633/restrict-post-navigation-to-current-sub-menu/102616#102616
 * @param int $post_id. Optional, defaults to the 'current' post.
 * @return array An array of 'parent' term IDs
 */
function wpse101633_get_parent_terms( $post_id = 0 ) {

    $post_id = ( $post_id ? $post_id : get_the_ID() );
    $terms = get_the_terms( $post_id, 'category' );
    $terms_with_children = array();

    if( $terms ){
       foreach( $terms as $t ) {
            if( $t->parent && !in_array(  $t->parent, $terms_with_children ) ){
                $terms_with_children[] = $t->parent;
            }
        }
    }

    return $terms_with_children ;
}

Example usage:

Health warning: I don’t perform any checks so you may get errors if the post has no terms etc. The following logic to get $exclude_these should be wrapped up in a function and moved out of the template for readability. I got lazy.

<?php if (is_single() ) { 

 $all_term_ids = get_terms( 'category', array( 'fields' => 'ids' ) );
 $post_terms = get_the_terms( get_the_ID(), 'category' );
 $post_terms = wp_list_pluck( $post_terms, 'term_id' );
 $parent_post_terms = wpse101633_get_parent_terms();
 $child_terms = array_diff( $post_terms, $parent_post_terms );

 //Exclude terms not associated with post, or that have a child who do.
 $exclude_these = array_diff( $all_term_ids, $child_terms );

 ?>
<nav class="post-navigation">
    <h1 class="visuallyhidden">Post Navigation</h1>
    <ul>
        <li class="prev"><?php previous_post_link( '%link', '%title', false, $exclude_these ); ?></li>
        <li class="next"><?php next_post_link( '%link', '%title', false, $exclude_these ); ?></li>
    </ul>
</nav>
<?php } ?>