double relative_error()

in src/hit/common.cpp [59:102]


    double relative_error(const vector<double> &expected, const vector<double> &actual) {
        int len = expected.size();
        if (len != actual.size()) {
            LOG_AND_THROW_STREAM("Inputs to relative error do not have the same size: " << len
                                                                                        << " != " << actual.size());
        }

        Vector expected_vec = Vector(expected);
        Vector actual_vec = Vector(actual);
        Vector diff_vec = expected_vec - actual_vec;
        double expected_l2_norm = norm_2(expected_vec);
        double actual_l2_norm = norm_2(actual_vec);
        double diff_l2_norm = norm_2(diff_vec);

        // if the expected result is the zero vector, we can't reasonably compare norms.
        // We also can't just test if the expected vector norm is exactly 0 due to
        // decoding precision in CKKS. In other words, decode(encode(<0,0,...>))
        // may contain very small non-zero values. (Note that this has nothing to
        // do with encryption noise.) The "actual" result, which typically comes
        // from decryption a CKKS ciphertext, will have much larger coefficients.
        // For example, decoding noise for the all-0 vector may result in coefficients
        // with magnitude ~10^-30. Decryption of the all-0 vector will result in
        // coefficients ~10^-11. Since these are vastly different scales, the relative
        // norm is huge, even though these vectors both represent 0. As a result,
        // we instead fuzz the norm test: if the expected vector norm is "small enough"
        // we skip the comparison altogether. The magic constant below seems to work
        // well in practice.
        int log_norm_limit = 12;
        double max_allowed_l2_norm = pow(2, -log_norm_limit);
        if (expected_l2_norm <= max_allowed_l2_norm && actual_l2_norm <= max_allowed_l2_norm) {
            return -1;
        }

        if (expected_l2_norm <= max_allowed_l2_norm) {
            // An unexpected situation.
            LOG(WARNING) << "The expected result's norm is nearly zero (2^" << setprecision(8) << log2(expected_l2_norm)
                         << "), but the actual result's norm is non-zero (2^" << log2(actual_l2_norm) << ")";
        }
        if (diff_l2_norm > MAX_NORM) {
            LOG(WARNING) << "Relative norm is somewhat large (2^" << setprecision(8) << log2(diff_l2_norm)
                         << "); there may be an error in the computation.";
        }
        return diff_l2_norm;
    }