Esiste una (famiglia di) funzioni di rumore monotonicamente non decrescenti?


10

Vorrei che una funzione animasse un oggetto che si sposta dal punto A al punto B nel tempo, in modo tale che raggiunga B in un determinato momento, ma la sua posizione in qualsiasi momento è perturbata casualmente in modo continuo, ma non va mai indietro. Gli oggetti si muovono lungo linee rette, quindi ho bisogno solo di una dimensione.

Matematicamente, ciò significa che sto cercando un continuo f (x), x ∈ [0,1], tale che:

  • f (0) = 0
  • f (1) = 1
  • x <y → f (x) ≤ f (y)
  • Nella "maggior parte" i punti f (x + d) - f (x) non hanno alcuna relazione ovvia con d. (La funzione non è uniformemente crescente o altrimenti prevedibile; penso che sia anche equivalente a dire che nessun grado di derivata è una costante.)

Idealmente, in realtà vorrei un modo per avere una famiglia di queste funzioni, fornendo un certo stato del seme. Avrei bisogno di almeno 4 bit di seed (16 possibili funzioni), per il mio attuale utilizzo, ma dato che non è molto libero di fornire ancora di più.

Per evitare vari problemi con errori di accumulo, preferirei che la funzione non richiedesse alcun tipo di stato interno. Cioè, voglio che sia una funzione reale, non una "funzione" di programmazione.


3
Il tuo terzo e quarto requisito possono essere approssimati come f'(x)>0, quindi l'integrazione normalizzata del valore assoluto di qualsiasi funzione di rumore soddisferà tutti i tuoi requisiti. Sfortunatamente non conosco alcun modo semplice per calcolarlo, ma forse qualcun altro lo fa. :)
SkimFlux,

Sarebbe perturbante la perpendicolare della tua funzione di pendenza istantanea?
Kaoh

Quando dici "Per evitare vari problemi con errori di accumulo" pensavo che fossi preoccupato per la precisione. Sembra che, sulla base dei tuoi molti commenti, ti preoccupi del costo delle prestazioni di troppe valutazioni. Dovresti indicare esattamente a quali limiti di prestazioni e memoria siamo soggetti - il requisito è comunque inutile perché apparentemente si possono costruire funzioni con stato che non hanno errori di accumulo (Che cosa significa, comunque?). Inoltre, il tuo quarto punto è sbagliato. Un esempio banale: nessuna derivata di e ^ x è costante, quindi non equivale a dirlo.
Superbo

Risposte:


4

Per questo post, y = f (t) dove t è il parametro che si varia (tempo / avanzamento) e y è la distanza dall'obiettivo. Quindi parlerò in termini di punti su grafici 2D in cui l'asse orizzontale è tempo / progresso e la verticale è distanza.

Penso che puoi creare una curva cubica di Bezier con il primo punto in (0, 1) e il quarto (ultimo) punto in (1, 0). I due punti centrali possono essere posizionati casualmente (x = rand, y = rand) all'interno di questo rettangolo 1 per 1. Non riesco a verificarlo analiticamente, ma solo giocando con un'applet (sì, vai avanti e ridi) sembra che la curva di Bezier non diminuirà mai con un tale vincolo.

Questa sarà la tua funzione elementare b (p1, p2) che fornisce un percorso non decrescente dal punto p1 al punto p2.

Ora puoi generare ab (p (1) = (0, 1), p (n) = (1, 0)) e scegliere un numero di p (i) lungo questa curva in modo che 1

In sostanza, stai generando un percorso "generale", quindi lo spezzi in segmenti e rigeneri ogni segmento.

Poiché vuoi una funzione matematica: Supponi che la procedura sopra descritta sia impacchettata in una funzione y = f (t, s) che ti dà la distanza at per la funzione di seme s. Avrai bisogno:

  • 4 numeri casuali per posizionare i 2 punti centrali della spline di Bezier principale (da (0, 1) a (1, 0))
  • n-1 numeri per i limiti di ciascun segmento se si hanno n segmenti (il primo segmento inizia sempre da (0, 1) cioè t = 0 e l'ultimo termina da (1,0) cioè t = 1)
  • 1 numero se si desidera randomizzare il numero di segmenti
  • Altri 4 numeri per posizionare i punti medi della spline del segmento in cui atterra

Quindi ogni seme deve fornire uno dei seguenti:

  • 7 + n numeri reali compresi tra 0 e 1 (se si desidera controllare il numero di segmenti)
  • 7 numeri reali e un numero intero maggiore di 1 (per un numero casuale di segmenti)

Immagino che tu possa realizzare uno di questi semplicemente fornendo una serie di numeri come seme. In alternativa, potresti fare qualcosa come fornire un numero s come seme, quindi chiamare il generatore di numeri casuali incorporato con rand (s), rand (s + 1), rand (s + 2) e così via (o inizializzare con se continua a chiamare rand.NextNumber).

Si noti che anche se l'intera funzione f (t, s) è composta da molti segmenti, si sta valutando solo un segmento per ogni t. Si avrà bisogno di calcolare più volte i confini di segmenti con questo metodo, perché si dovrà ordinare loro di non fare sicuro due segmenti si sovrappongono. Probabilmente puoi ottimizzare e sbarazzarti di questo lavoro extra e trovare solo gli endpoint di un segmento per ogni chiamata, ma non è ovvio per me in questo momento.

Inoltre, le curve di Bezier non sono necessarie, qualsiasi spline che si comporti in modo adeguato farà.

Ho creato un'implementazione di esempio di Matlab.

La funzione Bezier (vettorializzata):

function p = bezier(t, points)
% p = bezier(t, points) takes 4 2-dimensional points defined by 2-by-4 matrix
% points and gives the value of the Bezier curve between these points at t.
% 
% t can be a number or 1-by-n vector. p will be an n-by-2 matrix.
    coeffs = [
        (1-t').^3, ...
        3*(1-t').^2.*t', ...
        3*(1-t').*t'.^2, ...
        t'.^3
    ];

    p = coeffs * points;
end

La funzione composta di Bezier sopra descritta (volutamente lasciata non vettorizzata per chiarire quanta valutazione è necessaria per ogni chiamata):

function p = bezier_compound(t, ends, s)
% p = bezier(t, points) takes 2 2-dimensional endpoints defined by a 2-by-2
% matrix ends and gives the value of a "compound" Bezier curve between
% these points at t.
% 
% t can be a number or 1-by-n vector. s must be a 1-by-7+m vector of random
% numbers from 0 to 1. p will be an n-by-2 matrix. 
    %% Generate a list of segment boundaries
    seg_bounds = [0, sort(s(9:end)), 1];

    %% Find which segment t falls on
    seg = find(seg_bounds(1:end-1)<=t, 1, 'last');

    %% Find the points that segment boundaries evaluate to
    points(1, :) = ends(1, :);
    points(2, :) = [s(1), s(2)];
    points(3, :) = [s(3), s(4)];
    points(4, :) = ends(2, :);

    p1 = bezier(seg_bounds(seg), points);
    p4 = bezier(seg_bounds(seg+1), points);

    %% Random middle points
    p2 = [s(5), s(6)] .* (p4-p1) + p1;
    p3 = [s(7), s(8)] .* (p4-p1) + p1;

    %% Gather together these points
    p_seg = [p1; p2; p3; p4];

    %% Find what part of this segment t falls on
    t_seg = (t-seg_bounds(seg))/(seg_bounds(seg+1)-seg_bounds(seg));

    %% Evaluate
    p = bezier(t_seg, p_seg);    
end

Lo script che traccia la funzione per un seme casuale (nota che questo è l'unico posto in cui viene chiamata una funzione casuale, le variabili casuali a tutti gli altri codici vengono propagate da questo unico array casuale):

clear
clc

% How many samples of the function to plot (higher = higher resolution)
points = 1000;

ends = [
    0, 0;
    1, 1;
    ];

% a row vector of 12 random points
r = rand(1, 12);

p = zeros(points, 2);

for i=0:points-1
    t = i/points;
    p(i+1, :) = bezier_compound(t, ends, r);
end

% We take a 1-p to invert along y-axis here because it was easier to
% implement a function for slowly moving away from a point towards another.
scatter(p(:, 1), 1-p(:, 2), '.');
xlabel('Time');
ylabel('Distance to target');

Ecco un esempio di output:

inserisci qui la descrizione dell'immagine

Sembra soddisfare la maggior parte dei tuoi criteri. Tuttavia:

  • Ci sono "angoli". Ciò può essere reso possibile utilizzando le curve di Bezier in modo più appropriato.
  • "Ovviamente" sembra spline, anche se non puoi davvero indovinare cosa farà dopo un periodo di tempo non banale se non conosci il seme.
  • Molto raramente devia troppo verso l'angolo (può essere risolto giocando con la distribuzione del generatore di semi).
  • La funzione cubica di Bezier non può raggiungere un'area vicino all'angolo dati questi vincoli.

1

Immagino che invece di fondere un sacco di coseni trasformati (come i prodotti a punti nel rumore del perlin ti danno), potresti fondere diverse funzioni monotoniche che iniziano con f (0) = 0, come f (x) = x, o 2x, o x ^ 2, ecc. Infatti, poiché il tuo dominio è limitato a 0 => 1, potresti anche fondere funzioni trig che si adattano al conto all'interno di quel dominio come cos (90 * x + 270). Per normalizzare i tuoi metodi per finire con 1, puoi semplicemente dividere la somma ponderata di questi metodi monotonici che iniziano con f (0) = 0 per f (1). Qualcosa del genere dovrebbe essere anche abbastanza facile da invertire (che, a mio avviso, desideri dalle funzioni reali senza stato rispetto alle funzioni di programmazione).

Spero che sia di aiuto.


1

Si può analizzare questa immagine rozza. inserisci qui la descrizione dell'immagine Puoi finire con una funzione che esegue la tua animazione al volo, usando una funzione rand uniforme. So che questa non è la formula matematica esatta, ma in realtà non esiste una formula matematica per una funzione casuale, e anche se ce ne fosse una, dovresti codificare molto per raggiungere questo obiettivo. Considerando che non è stata specificata alcuna condizione di scorrevolezza, il profilo di velocità è $ C ^ 0 $ continuo (ma poiché non si ha a che fare con i robot, non è necessario preoccuparsi dei profili di accelerazione discontinui).


"In realtà non esiste una formula matematica per una funzione casuale" Voglio una funzione di rumore, non una funzione casuale. Le funzioni di rumore sono ben documentate per esistere. Definizioni a tratti come questa tendono anche a creare inefficienza (la valutazione diventa O (pezzi) che diventa un problema quando si hanno scale temporali lunghe), funzioni impure (valutare in O (1) ma è necessario mantenere la posizione precedente) oppure vincolare possibili funzioni (ad es. tutti i punti di flesso sono ad intervalli fissi).

Hmm, scusa, ho pensato che le funzioni di rumore usassero anche una procedura di generazione di numeri casuali e che dipendessero anche da un insieme discreto di punti guida / chiave per ottenere una forma (ho visto che Perlin Noise è stato menzionato ... che uno funziona tramite pseudo-casuale generatori di numeri che sono piuttosto difficili da integrare, quindi nessuna soluzione analitica). È possibile integrare analiticamente una funzione di rumore? Mi chiedo se uno di questi potrebbe essere un collegamento
teodron

Ad esempio, il rumore di Perlin assume uno stato seed di 255 numeri a 8 bit, ma da ciò genera rumore casuale a distanza infinita in tre dimensioni; non è proprio preciso descriverli come "punti guida", matematicamente sono più come altri 256 parametri che non si desidera continuare a fornire. Come dici tu non è sostanzialmente integrabile, ma è una funzione pura. La pagina a cui ti sei collegato è una cattiva spiegazione del rumore di Perlin (non è proprio il rumore di Perlin che spiega). Per quanto riguarda se è possibile per qualche tipo di funzione del rumore ... beh, questa è la domanda, no?

1

Il solito modo di generare una sequenza crescente di N numeri casuali da [0,1] è generare N numeri casuali in qualsiasi intervallo, quindi dividerli tutti per la loro somma totale, quindi sommarli uno alla volta per ottenere il sequenza.

Genera la sequenza 2, 2, 5, 8, 6.
La loro somma è 23, quindi i nostri numeri da sommare sono 2/23, 2/23, 5/23, 8/23 e 6/23.
La nostra sequenza finale è 2/23, 4/23, 9/23, 17/23, 23/23

Questo può essere esteso al 2D generando questi valori sia per X che per Y. Puoi aumentare N per ottenere la granularità che desideri.


Nella risposta simile di @ teodron, hai citato problemi di efficienza con scale temporali di grandi dimensioni. Senza conoscere il vero problema che stai affrontando, non posso dire se tale preoccupazione è valida; ma un'altra opzione sarebbe quella di generare per N piccola e semplicemente lisciare il risultato. A seconda dell'applicazione, ciò può effettivamente dare risultati migliori .

inserisci qui la descrizione dell'immagine
N = 100, nessun livellamento

inserisci qui la descrizione dell'immagine
N = 15, con levigatura


Qualunque cosa tu stia facendo per il livellamento, sembra che il risultato non abbia nemmeno una funzione (intorno a x = 0,95); Non sono sicuro che si tratti di un artefatto del tuo programma di rappresentazione grafica o di un errore. Anche la monotonicità sembra essere violata intorno allo 0,7. Ad ogni modo, ho familiarità con "il solito modo" - sto ponendo questa domanda perché sospetto che il solito modo sia scadente. Il rumore pre-perlinico, dopo tutto, nessuno aveva un problema con LUT gigantesche di rumore di valore, era solo "il solito modo". Oggi abbiamo un modo che è considerevolmente più flessibile ed efficiente.

3
Concordo con BlueRaja: esistono metodi ben noti e facili da implementare per smussare senza violare la monotonia, indipendentemente dall'esempio. Ad esempio, spostando la media o disegnando spline. Tuttavia, la preoccupazione di @JoeWreschnig non è irrilevante. Le regole e la meccanica del gioco possono dipendere da oggetti che non si ritirano mai per funzionare - raramente è una buona idea presumere che le cose che il richiedente non ha realmente bisogno di ciò di cui ha bisogno.
Superbo

1
@BlueRaja: le mie lamentele di base su approcci a tratti come questo sono descritte nella mia risposta a teodrone. Non si tratta di trovare "il risultato più rigido e matematicamente preciso", si tratta di aprire nuove possibilità con uno strumento matematico che prima ci era sconosciuto. Ancora una volta, considera l'analogia tra LUT di rumore di valore gigante e rumore di Perlin. Non tutte le domande sul sito hanno bisogno di una risposta "abbastanza buona" fuori dal comune qualsiasi studente CS intelligente a metà strada potrebbe sbattere tra le lezioni - a volte, spariamo per fare qualcosa di originale e professionale, ok?

1
Oppure potremmo semplicemente continuare a lasciare questo sito sguazzare nel 90% di confusione elementare sulle matrici di trasformazione, il 10% "aiutami a smettere di giocare!" Questo renderà un fantastico sito di domande e risposte che ogni professionista adorerà visitare.

2
@Joe: Ecco, erm, non richiesto. Hai chiesto una soluzione adatta ai tuoi criteri, te ne ho dato uno. Solo perché è semplice non lo rende cattivo.
BlueRaja - Danny Pflughoeft

1

Suggerisco questa implementazione ispirata alla sommatoria delle ottave trovate nel rumore frattale, con un po 'di culo a buon mercato che mescola qua e là. Credo che sia ragionevolmente veloce e possa essere sintonizzato chiedendo meno ottave rispetto a quelle memorizzate nei parametri con una perdita di precisione di circa 1/2^octave.

Potresti vederlo come un'implementazione a tratti che richiede solo tempo O (log (pezzi)) . L'array di parametri viene utilizzato sia per la posizione del perno di divisione e conquista, sia per la distanza percorsa quando si raggiunge il perno.

template<int N> struct Trajectory
{
    Trajectory(int seed = 0)
    {
        /* The behaviour can be tuned by changing 0.2 and 0.6 below. */
        if (seed)
            srand(seed);
        for (int i = 0; i < N; i++)
            m_params[i] = 0.2 + 0.6 * (double)(rand() % 4096) / 4096;
    }

    double Get(double t, int depth = N)
    {
        double min = 0.0, max = 1.0;
        for (int i = 0, dir = 0; i < N && i < depth; i++)
        {
            int j = (dir + 1 + i) % N;
            double mid = min + (max - min) * m_params[j];
            if (t < m_params[i])
            {
                dir += 1;
                t = t / m_params[i];
                max = mid;
            }
            else
            {
                dir ^= i;
                t = (t - m_params[i]) / (1.0 - m_params[i]);
                min = mid;
            }
        }
        t = (3.0 - 2.0 * t) * t * t; // Optional smoothing
        return min + (max - min) * t;
    }

    double m_params[N];
};

Potrebbe essere reso più veloce pre-calcolando le divisioni in virgola mobile, al costo di memorizzare il triplo di informazioni.

Questo è un rapido esempio:

cinque diverse traiettorie

L'esempio è stato ottenuto con il seguente codice:

for (int run = 0; run < 5; run++)
{
    /* Create a new shuffled trajectory */
    Trajectory<12> traj;

    /* Print dots */
    for (double t = 0; t <= 1.0; t += 0.0001)
        printf("%g %g\n", t, traj.Get(t));
}

0

Pensare ad alta voce e ammettere il calcolo non è il mio punto di forza ... forse questo non è possibile? Per evitare qualsiasi modello ovvio, la media della funzione del rumore su qualsiasi cambiamento in x deve essere vicina a zero e per garantire la monotonicità l'ampiezza del rumore su quel cambiamento in x deve essere inferiore alla variazione in x, poiché qualsiasi ampiezza maggiore potrebbe risulta in un valore più basso in x 'rispetto a x. Ciò significherebbe che quando si riduce dx verso 0, tale funzione deve anche ridurre dA (dove A è l'ampiezza) verso zero, il che significa che non si ottiene alcun contributo da alcuna funzione di rumore conforme.

Posso immaginare che sia possibile formulare una funzione che diminuisca gradualmente il contributo del rumore quando x si avvicina a 1, ma che ti darà una funzione curva che decelera quando x si avvicina a 1, che non è quello che penso tu voglia.


1
Riesco a disegnare milioni di grafici di tali funzioni e, come dice SkimFlux, l'integrazione di una funzione noise offre una funzione praticamente equivalente se la normalizzi. Quindi le funzioni esistono , è solo una questione se possono essere fattibili in codice . Quindi chiedendo qui invece di math.se.

Ad esempio, qualsiasi funzione che decelera quando x si avvicina a 1 ha una funzione "inversa" equivalente g(x) = 1 - f(1 - x), che invece accelera quando x parte 0.

Certo, le funzioni esistono - puoi disegnarne una come teodron - ma sono funzioni di "rumore"? Il rumore implica una funzione continua basata su input pseudo-casuali con un'ampiezza implicita rispetto a una linea di base. E se tale ampiezza è troppo elevata, non è possibile garantire che la differenza tra i passaggi sia abbastanza bassa da mantenere l'output monotonico. Ma mi viene in mente che la densità del rumore e il passaggio di interpolazione potrebbero essere realizzati per soddisfare le vostre specifiche, di cui ci penserò un po 'di più.
Kylotan,

Il rumore significa semplicemente "imprevedibile", non dice nulla sui metodi di generazione (o anche, tecnicamente, continuità, anche se per l'animazione si desidera quasi sempre un rumore coerente). È vero che gli endpoint fissi limitano in qualche modo la possibile ampiezza di questa funzione, ma non del tutto. Altre funzioni di rumore hanno proprietà simili, ad esempio Perlin (x) = 0 per qualsiasi numero intero x. La monotonia è una garanzia più forte di così, ma non penso che sia molto più forte da renderlo impossibile.

@JoeWreschnig Sono sicuro che sei consapevole che la funzione del rumore Perlin viola palesemente molti dei tuoi criteri. Innanzitutto passa attraverso 0 ai nodi della griglia, quindi f (x + d) -f (x) è un multiplo costante di d per alcuni x (spaziati regolarmente) x. Inoltre, a causa di quel trucco di cache intelligente, si ripeterà per griglie di grandi dimensioni. Per il rumore classico, penso che l'implementazione di riferimento dovrebbe avere il riquadro della griglia (x, y) identico al riquadro (x + 256, y + 256). Dovresti dichiarare se questo è accettabile e fino a che punto.
Superbo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.