"Приблизительный" наибольший общий делитель

Предположим, что у вас есть список чисел с плавающей запятой, которые являются приблизительно кратными общей величине, например

2.468, 3.700, 6.1699

которые примерно все кратные 1.234. Как бы вы охарактеризовали этот "приблизительный gcd" и как вы могли бы его вычислить или оценить?

Строго связано с моим ответом на этот вопрос.

Ответ 1

Вы можете запустить алгоритм Euclid gcd с чем-то меньшим, чем 0,01 (или небольшим числом по вашему выбору), являющимся псевдоопределением. С вашими номерами:

3.700 = 1 * 2.468 + 1.232,
2.468 = 2 * 1.232 + 0.004. 

Итак, псевдо gcd первых двух чисел равно 1.232. Теперь вы берете gcd этого с вашим последним номером:

6.1699 = 5 * 1.232 + 0.0099.

Итак, 1.232 - псевдо gcd, а mutiples - 2,3,5. Чтобы улучшить этот результат, вы можете взять линейную регрессию в точках данных:

(2,2.468), (3,3.7), (5,6.1699).

Наклон - улучшенный псевдо-gcd.

Предостережение: первая часть этого алгоритма численно неустойчива - если вы начинаете с очень грязных данных, у вас проблемы.

Ответ 2

Выразите свои измерения как кратные самому низкому. Таким образом, ваш список становится 1.00000, 1.49919, 2.49996. Дробные части этих значений будут очень близки к 1/Nths, для некоторого значения N, определяемого тем, насколько близко ваше наименьшее значение относится к основной частоте. Я предлагаю зацикливаться на увеличении N до тех пор, пока вы не найдете достаточно изысканный матч. В этом случае для N = 1 (т.е. Если X = 2.468 - ваша основная частота), вы найдете стандартное отклонение 0,33333 (два из трех значений равны .5 от X * 1), что недопустимо велико. Для N = 2 (т.е. Если 2.468/2 - ваша основная частота), вы найдете стандартное отклонение практически нулевого значения (все три значения находятся в пределах .001 кратного X/2), таким образом, 2.468/2 является вашим приблизительным НОД.

Главным недостатком моего плана является то, что он лучше всего работает, когда самое низкое измерение является самым точным, что, вероятно, не так. Это можно было бы смягчить, выполняя всю операцию несколько раз, каждый раз отбрасывая самое низкое значение в списке измерений, а затем используйте список результатов каждого прохода, чтобы определить более точный результат. Другим способом уточнения результатов будет настройка GCD для минимизации стандартного отклонения между целыми кратными GCD и измеренными значениями.

Ответ 3

Это напоминает мне проблему нахождения хороших аппроксимаций рациональных чисел действительных чисел. Стандартная методика - это разложение продолжительной доли:

def rationalizations(x):
    assert 0 <= x
    ix = int(x)
    yield ix, 1
    if x == ix: return
    for numer, denom in rationalizations(1.0/(x-ix)):
        yield denom + ix * numer, numer

Мы могли бы применить это непосредственно к подходам Джонатана Леффлера и Спарра:

>>> a, b, c = 2.468, 3.700, 6.1699
>>> b/a, c/a
(1.4991896272285252, 2.4999594813614263)
>>> list(itertools.islice(rationalizations(b/a), 3))
[(1, 1), (3, 2), (925, 617)]
>>> list(itertools.islice(rationalizations(c/a), 3))
[(2, 1), (5, 2), (30847, 12339)]

отбирает первое достаточно хорошее приближение из каждой последовательности. (3/2 и 5/2 здесь.) Или вместо прямого сравнения 3.0/2.0 до 1.499189... вы могли заметить, что 925/617 использует гораздо большие целые числа, чем 3/2, что делает 3/2 отличным местом для остановки.

Не важно, какое из чисел вы разделите. (Например, с помощью a/b и c/b вы получаете 2/3 и 5/3.) После того, как вы получите целочисленные коэффициенты, вы можете уточнить предполагаемую оценку фундаментальной линейной регрессии shsmurfy. Все побеждают!

Ответ 4

Я предполагаю, что все ваши числа кратно значениям integer. В оставшейся части моего объяснения A будет обозначать "корневую" частоту, которую вы пытаетесь найти, а B будет массивом чисел, с которых вы должны начать.

То, что вы пытаетесь сделать, внешне похоже на линейную регрессию . Вы пытаетесь найти линейную модель y = mx + b, которая минимизирует среднее расстояние между линейной моделью и набором данных. В вашем случае b = 0, m - частота корня, y - заданные значения. Самая большая проблема заключается в том, что независимые переменные X явно не заданы. Единственное, что мы знаем о X, это то, что все его члены должны быть целыми числами.

Ваша первая задача - определить эти независимые переменные. Лучший способ, о котором я могу думать, в настоящий момент предполагает, что данные частоты имеют почти последовательные индексы (x_1=x_0+n). Итак, B_0/B_1=(x_0)/(x_0+n) задано (надеюсь) маленькое целое число n. Вы можете воспользоваться тем фактом, что x_0 = n/(B_1-B_0), начните с n = 1 и продолжайте держать его вверх до тех пор, пока k-rnd (k) не окажется в пределах определенного порога. После того, как вы установили x_0 (начальный индекс), вы можете приблизиться к частоте корня (A = B_0/x_0). Затем вы можете аппроксимировать другие индексы, найдя x_n = rnd(B_n/A). Этот метод не очень надежный и, вероятно, сбой, если ошибка в данных велика.

Если вы хотите лучше приблизить частоту корня A, вы можете использовать линейную регрессию, чтобы минимизировать ошибку линейной модели, теперь, когда у вас есть соответствующие зависимые переменные. Самый простой способ для этого - использовать финал наименьших квадратов. Вольфрам Mathworld имеет углубленное математическое рассмотрение проблемы, но справедливо простое объяснение можно найти с помощью некоторых поисковых запросов.

Ответ 5

Интересный вопрос... нелегко.

Я полагаю, что я посмотрю на отношения значений выборки:

  • 3.700/2.468 = 1.499...
  • 6.1699/2.468 = 2.4999...
  • 6.1699/3.700 = 1.6675...

И тогда я бы искал простое соотношение целых чисел в этих результатах.

  • 1.499 ~ = 3/2
  • 2.4999 ~ = 5/2
  • 1.6675 ~ = 5/3

Я не прогонял это, но где-то вдоль линии вы решили, что ошибка 1:1000 или что-то достаточно хорошо, и вы назад, чтобы найти базовый примерный GCD.

Ответ 6

Решение, которое я видел и использовал сам, состоит в том, чтобы выбрать некоторую константу, скажем 1000, умножить все числа на эту константу, округлить их до целых чисел, найти GCD этих целых чисел, используя стандартный алгоритм, а затем разделить результат на указанная константа (1000). Чем больше константа, тем выше точность.

Ответ 7

Это реформация решения shsmurfy, когда вы априори выбираете 3 положительных допусков (e1, e2, e3)
Задача состоит в том, чтобы искать наименьшие положительные целые числа (n1, n2, n3) и, следовательно, самую большую частоту корня f такую, что:

f1 = n1*f +/- e1
f2 = n2*f +/- e2
f3 = n3*f +/- e3

Предположим, что 0 <= f1 <= f2 <= f3 Если мы зафиксируем n1, то получим следующие соотношения:

f  is in interval I1=[(f1-e1)/n1 , (f1+e1)/n1]
n2 is in interval I2=[n1*(f2-e2)/(f1+e1) , n1*(f2+e2)/(f1-e1)]
n3 is in interval I3=[n1*(f3-e3)/(f1+e1) , n1*(f3+e3)/(f1-e1)]

Начнем с n1 = 1, затем увеличим n1 до тех пор, пока интервал I2 и I3 не будет содержать целое число, то есть floor(I2min) different from floor(I2max) то же самое с I3
Затем мы выбираем наименьшее целое число n2 в интервале I2 и наименьшее целое число n3 в интервале I3.

Предполагая нормальное распределение ошибок с плавающей запятой, наиболее вероятной оценкой частоты корня f является минимизация

J = (f1/n1 - f)^2 + (f2/n2 - f)^2 + (f3/n3 - f)^2

Это

f = (f1/n1 + f2/n2 + f3/n3)/3

Если в интервалах I2, I3 имеется несколько целых чисел n2, n3, мы также можем выбрать пару, которая минимизирует остаток

min(J)*3/2=(f1/n1)^2+(f2/n2)^2+(f3/n3)^2-(f1/n1)*(f2/n2)-(f1/n1)*(f3/n3)-(f2/n2)*(f3/n3)

Другим вариантом может быть продолжение итерации и попытка минимизировать другой критерий, такой как min (J (n1)) * n1, пока f не опустится ниже определенной частоты (n1 достигает верхнего предела)...

Ответ 8

Я нашел этот вопрос в поисках ответов на мой вопрос в MathStackExchange (здесь и здесь).

Мне удалось (пока) измерить привлекательность основной частоты по списку гармонических частот (следуя номенклатуре звука/музыки), что может быть полезно, если у вас меньше вариантов и возможно вычислить апелляцию каждого из них, а затем выбрать наиболее подходящий.

C & P из моего вопроса в MSE (там форматирование красивее):

  • будучи v списком {v_1, v_2,..., v_n}, упорядоченным снизу вверх
  • mean_sin (v, x) = сумма (sin (2 * pi * v_i/x), для я в {1,..., n})/n
  • mean_cos (v, x) = сумма (cos (2 * pi * v_i/x), для я в {1,..., n})/n
  • gcd_appeal (v, x) = 1 - sqrt (mean_sin (v, x) ^ 2 + (mean_cos (v, x) - 1) ^ 2)/2, что дает число в интервале [0,1].

Цель состоит в том, чтобы найти х, который максимизирует привлекательность. Вот график (gcd_appeal) для вашего примера [2.468, 3.700, 6.1699], где вы обнаружите, что оптимальный GCD находится при x = 1.2337899957639993 enter image description here

Редактировать: вам может пригодиться этот код JAVA для расчета (нечеткой) делимости (также известной как gcd_appeal) делителя относительно списка дивидендов; Вы можете использовать его, чтобы проверить, какой из ваших кандидатов является лучшим делителем. Код выглядит некрасиво, потому что я пытался оптимизировать его для повышения производительности.

    //returns the mean divisibility of dividend/divisor as a value in the range [0 and 1]
    // 0 means no divisibility at all
    // 1 means full divisibility
    public double divisibility(double divisor, double... dividends) {
        double n = dividends.length;
        double factor = 2.0 / divisor;
        double sum_x = -n;
        double sum_y = 0.0;
        double[] coord = new double[2];
        for (double v : dividends) {
            coordinates(v * factor, coord);
            sum_x += coord[0];
            sum_y += coord[1];
        }
        double err = 1.0 - Math.sqrt(sum_x * sum_x + sum_y * sum_y) / (2.0 * n);
        //Might happen due to approximation error
        return err >= 0.0 ? err : 0.0;
    }

    private void coordinates(double x, double[] out) {
        //Bhaskara performant approximation to
        //out[0] = Math.cos(Math.PI*x);
        //out[1] = Math.sin(Math.PI*x);
        long cos_int_part = (long) (x + 0.5);
        long sin_int_part = (long) x;
        double rem = x - cos_int_part;
        if (cos_int_part != sin_int_part) {
            double common_s = 4.0 * rem;
            double cos_rem_s = common_s * rem - 1.0;
            double sin_rem_s = cos_rem_s + common_s + 1.0;
            out[0] = (((cos_int_part & 1L) * 8L - 4L) * cos_rem_s) / (cos_rem_s + 5.0);
            out[1] = (((sin_int_part & 1L) * 8L - 4L) * sin_rem_s) / (sin_rem_s + 5.0);
        } else {
            double common_s = 4.0 * rem - 4.0;
            double sin_rem_s = common_s * rem;
            double cos_rem_s = sin_rem_s + common_s + 3.0;
            double common_2 = ((cos_int_part & 1L) * 8L - 4L);
            out[0] = (common_2 * cos_rem_s) / (cos_rem_s + 5.0);
            out[1] = (common_2 * sin_rem_s) / (sin_rem_s + 5.0);
        }
    }