def test_pass_followed_by_fp16(self):
        """
        Input:

            x
            -> transpose_1(torch_scope="module_1")
            -> transpose_2(torch_scope="module_2")
            -> output

        Output:

            x
            -> cast(
                    dtype="fp16",
                    torch_scope="module_2",
                    pass_scope=["merge_consecutive_transposes", "add_fp16_cast"]
               )
            -> transpose_3_fp16(
                    torch_scope="module_2",
                    pass_scope=["merge_consecutive_transposes", "add_fp16_cast"]
               )
            -> cast(dtype="fp32",
                    torch_scope="module_2",
                    pass_scope=["merge_consecutive_transposes", "add_fp16_cast"]
               )
            -> output

        In the above case, two transpose ops first merged into a single transpose op,
        and the graph is transformed into fp16.

        Hence, the final transpose op should have scope information from both graph passes.
        """

        @mb.program(input_specs=[mb.TensorSpec(shape=(1, 2, 3, 4))])
        def prog(x):
            with mb.scope(
                ScopeInfo(source=ScopeSource.TORCHSCRIPT_MODULE_TYPE, data="module_1"),
            ):
                x = mb.transpose(x=x, perm=[0, 2, 1, 3])
            with mb.scope(
                ScopeInfo(source=ScopeSource.TORCHSCRIPT_MODULE_TYPE, data="module_2"),
            ):
                return mb.transpose(x=x, perm=[3, 2, 0, 1])

        prog._add_essential_scope_source(ScopeSource.TORCHSCRIPT_MODULE_TYPE)

        apply_pass_and_basic_check(prog, "common::merge_consecutive_transposes")
        apply_pass_and_basic_check(prog, "common::add_fp16_cast")
        apply_pass_and_basic_check(prog, "common::cast_optimization")
        apply_pass_and_basic_check(prog, "common::dead_code_elimination")

        assert get_op_types_in_program(prog) == ["cast", "transpose", "cast"]

        cast_ops = prog.find_ops(op_type="cast")
        assert len(cast_ops) == 2
        assert len(cast_ops[0].scopes) == 2
        assert cast_ops[0].scopes[ScopeSource.COREMLTOOLS_GRAPH_PASS] == [
            "merge_consecutive_transposes",
            "add_fp16_cast",
        ]
        assert cast_ops[0].scopes[ScopeSource.TORCHSCRIPT_MODULE_TYPE] == ["module_2"]
        assert len(cast_ops[1].scopes) == 2
        assert cast_ops[1].scopes[ScopeSource.COREMLTOOLS_GRAPH_PASS] == [
            "merge_consecutive_transposes",
            "add_fp16_cast",
        ]
        assert cast_ops[1].scopes[ScopeSource.TORCHSCRIPT_MODULE_TYPE] == [
            "module_2",
        ]

        transpose_op = prog.find_ops(op_type="transpose")[0]
        assert len(transpose_op.scopes) == 2
        assert transpose_op.scopes[ScopeSource.COREMLTOOLS_GRAPH_PASS] == [
            "merge_consecutive_transposes",
            "add_fp16_cast",
        ]
        assert transpose_op.scopes[ScopeSource.TORCHSCRIPT_MODULE_TYPE] == [
            "module_2",
        ]

        Input:

            x
            -> transpose_1(torch_scope="module_1")
            -> transpose_2(torch_scope="module_2")
            -> output

        Output:

            x
            -> cast(
                    dtype="fp16",
                    torch_scope="module_2",
                    pass_scope=["merge_consecutive_transposes", "add_fp16_cast"]
               )
            -> transpose_3_fp16(
                    torch_scope="module_2",
                    pass_scope=["merge_consecutive_transposes", "add_fp16_cast"]
               )
            -> cast(dtype="fp32",
                    torch_scope="module_2",
                    pass_scope=["merge_consecutive_transposes", "add_fp16_cast"]
               )
            -> output

        In the above case, two transpose ops first merged into a single transpose op,
        and the graph is transformed into fp16.

        Hence, the final transpose op should have scope information from both graph passes.
        
coremltools/converters/mil/mil/passes/tests/test_passes.py