def gbc_discre_for_N_K_Q_fixed_l(N, K, Q, l1=1):

    print "N = ", N, ", K = ", K, ", Q = ", Q

    e_itkN_arr = calculate_e_itkN_arr(K, N)

    print gbc_con_6_upto_target[(N - 6) / 2]
    gbc_con_expect = gbc_con_6_upto_target[(N - 6) / 2][1]
    assert gbc_con_6_upto_target[(N - 6) / 2][0] == N
    print "N = %d, gbc_con_expect = %d" % (N, gbc_con_expect)

    # q_good_list[] will hold a set of q ( 2 <= q <= Q) and
    q_good_list = []

    q = Q

    # For easy counting, D_N_K_q_2d_arr include q = 0, q =1, which are always 0
    # and it will go up to q (include q).
    Shape = [q + 1, 3]
    D_N_K_q_2d_arr = np.zeros(Shape, dtype=np.complex64)

    # record main term, error term, and gbc_con_expect_N_may_larger_than_K_fixed_l for every q with fixed l
    main_term_arr = np.zeros(q + 1, dtype=np.complex64)
    error_term_arr = np.zeros(q + 1, dtype=np.complex64)
    gbc_con_expect_N_may_larger_than_K_fixed_l_arr = np.zeros(q + 1, dtype=np.complex64)

    for q in range(2, Q + 1):

        l2 = (N - 1) % q

        # if (N-1)%q is not co-prime with q, this will make things complicated if we try to find out a good
        # model for it, let's skip such q for now.
        if gcd(l2, q) != 1:
            continue

        # sieg_walf can NOT be used when (q%K != 0)
        if q % K != 0:
            continue

        e_itkl_2d_arr = calculate_e_itkl_2d_arr(K, q)

        # get gbc_con directly by counting. This function allows N > K and N <= K
        gbc_con_expect_N_may_larger_than_K_fixed_l = D_N_K_counting_direct_fixed_l(
            N, K, q, gbc_con_expect, l1, print_option=False
        )

        main_term_for_D_N_q, error_term_for_D_N_q, is_q_good, error_term_with_phase_for_D_N_q, is_q_good_with_error_cancellation = D_N_K_q_fixed_l(
            N, K, q, e_itkN_arr, e_itkl_2d_arr, gbc_con_expect_N_may_larger_than_K_fixed_l
        )
        D_N_K_q_2d_arr[q][0] = main_term_for_D_N_q
        D_N_K_q_2d_arr[q][1] = error_term_for_D_N_q
        D_N_K_q_2d_arr[q][2] = is_q_good

        main_term_arr[q] = main_term_for_D_N_q
        error_term_arr[q] = error_term_for_D_N_q
        gbc_con_expect_N_may_larger_than_K_fixed_l_arr[q] = gbc_con_expect_N_may_larger_than_K_fixed_l

        if is_q_good > 0:
            q_good_list.append([q, main_term_for_D_N_q, error_term_for_D_N_q])

    # plotting
    plt.figure()
    q_range = range(0, Q + 1)
    plot_one_list(main_term_arr.real, "main_term_arr.real", q_range, "ro-")
    plot_one_list(error_term_arr.real, "error_term_arr.real", q_range, "bx-")
    plot_one_list(
        gbc_con_expect_N_may_larger_than_K_fixed_l_arr.real,
        "gbc_con_expect_N_may_larger_than_K_fixed_l_arr.real",
        q_range,
        "gd-",
    )
    title_str = (
        "D_N_K_q_fixed_l: main: Red, error: blue, gbc_con_expect_fixed_l, green, gbc_con_expect = %d, N = %d, K = %d, Q = %d"
        % (gbc_con_expect, N, K, Q)
    )
    plt.title(title_str)
    plt.xlabel("q range")
    plt.show(block=False)
def D_N_K_search_K_for_fixed_N_q_l_for_N_list(
    q, N_list, only_search_1st_k=True, K_start=6, K_end=400, print_option=False
):

    print "D_N_K_search_K_for_fixed_N_q_l,  K range(%d, %d), q = %d, tests start... " % (K_start, K_end, q)
    print "N_list = ", N_list

    N_K_good_list = []
    skip_N_list = []
    N_cnt = 0
    N_K_good_cnt = 0

    for N in N_list:

        # N only allow even number
        if N % 2 == 1:
            continue

        N_cnt += 1

        # we will be only interested in the case that l2 is co-prime with q
        l2 = (N - 1) % q
        if gcd(l2, q) != 1:
            skip_N_list.append(N)
            continue

        gbc_con_expect = gbc_con_6_upto_target[(N - 6) / 2][1]

        for K in range(K_start, K_end):

            # because of sieg_walf, q must be the multiplier of K
            if q % K != 0:
                continue

            e_itkN_arr = calculate_e_itkN_arr(K, N)
            e_itkl_2d_arr = calculate_e_itkl_2d_arr(K, q)

            # get D_N_K_q_fixed_l() and D_N_K_counting_direct_fixed_l()
            main_term_for_D_N_q_K, error_term_for_D_N_q_K, is_N_K_good, error_term_with_phase_for_D_N_q, is_q_good_with_error_cancellation = D_N_K_q_fixed_l(
                N, K, q, e_itkN_arr, e_itkl_2d_arr, gbc_con_expect, print_option=False
            )
            gbc_con_expect_N_may_larger_than_K_counting_direct_fixed_l = D_N_K_counting_direct_fixed_l(
                N, K, q, gbc_con_expect, l1=1, print_option=False
            )

            if print_option:
                print "***** N = ", N, ", K = ", K, "q = ", q, ",  main_term_for_D_N_q_K = ", main_term_for_D_N_q_K, ", gbc_con_expect_N_may_larger_than_K_counting_direct_fixed_l = ", gbc_con_expect_N_may_larger_than_K_counting_direct_fixed_l, ", gbc_con_expect = ", gbc_con_expect

            # we try to assert main_term_for_D_N_q_K.real <= gbc_con_expect_N_may_larger_than_K_counting_direct_fixed_l
            # but numerical round issues requires we loose the condition a bit. + 0.1 is needed in case that
            # gbc_con_expect_N_may_larger_than_K_counting_direct_fixed_l = 0
            # assert(main_term_for_D_N_q_K.real <= 1.1*(gbc_con_expect_N_may_larger_than_K_counting_direct_fixed_l + 0.1))

            if is_N_K_good > 0:
                N_K_good_list.append([N, K, q, main_term_for_D_N_q_K, error_term_for_D_N_q_K])
                if only_search_1st_k == True:
                    N_K_good_cnt += 1
                    break

    print "\nq = %d, l1 = 1, fixed: N_K_good_list.append([N, K, q, main_term_for_D_N_q_K, error_term_for_D_N_q_K]) = \n" % (
        q
    ), np.asarray(
        N_K_good_list
    )
    print "skip_N_list = ", skip_N_list
    failed_cnt = N_cnt - N_K_good_cnt - len(skip_N_list)
    print "N_cnt = %d, N_K_good_cnt %d, len(skip_N_list) = %d, failed_cnt = %d, q = %d" % (
        N_cnt,
        N_K_good_cnt,
        len(skip_N_list),
        failed_cnt,
        q,
    )
    print "D_N_K_search_K_for_fixed_N_q_l, K range(%d, %d), q = %d, fixed l1 = 1, done !" % (K_start, K_end, q)

    return N_cnt, N_K_good_cnt, skip_N_list, failed_cnt, N_K_good_list