Scalare un albero con NestList!

Visualizzare un albero

Lo scopo di questo esercizio è quello di prendere confidenza con il fatto che i grafici generati da Mathematica sono oggetti a tutti gli effetti e in quanto tali possono essere utilizzati per costruire oggetti (grafici) complessi. In particolare, l'esercitazione consiste nel disegnare degli alberi iterando l'applicazione di una funzione: partendo dal "tronco" dell'albero, questo viene riprodotto in due sue copie dalle dimensioni scalate, opportunamente ruotate. Ripetendo su ogni copia il procedimento per un certo numero di iterazioni si ottiene l'albero.

Definiamo una matrice di rotazione parametrica e due parametri, legati rispettivamente agli angoli di rotazione e alla scala che verranno applicati a un generico componente dell'albero.

rotate2D[vec_, 
        theta_] := {{Cos[theta], -Sin[theta]}, {Sin[theta], 
            Cos[theta]}} . vec

rotation = 0.3;
shrinkage = 0.8;

Definiamo poi la funzione twinLine che verrà applicata iterativamente:

twinLine[
        Line[{start_, 
            end_}]] := 
{
Line[{end, 
            end + shrinkage rotate2D[end - start, 
                  rotation]}
], 
Line[{end, 
            end + shrinkage rotate2D[
                  end - start, -rotation]}
]
}

Cosa succede se partiamo da un generico segmento, diciamo di estremi (0, 0) e (1, 1) e applichiamo una volta twinLine? Visualizzate in uno stesso grafico sia il segmento di partenza che quelli prodotti dall'applicazione della funzione.

soluzione

Show[
        Graphics[{Line[{{0, 0}, {1, 1}}], 
            twinLine[Line[{{0, 0}, {1, 1}}]]}]];

Definiamo ora una funzione che applica twinLine a tutti gli elementi di una lista e ritorna la lista contenente i valori ottenuti:

doTwins[lines_] := Map[twinLine, lines]

doTwins[{Line[{{0, 0}, {1, 1}}], 
        Line[{{0, 0}, {-1, 1}}]}]

L'output di doTwins è essenzialmente una lista multidimensionale. Modificate la funzione in modo che ritorni una lista di oggetti Line (suggerimento: usate la funzione Flatten).

soluzione

doTwins[lines_] := Flatten[Map[twinLine, lines]]

doTwins[{Line[{{0, 0}, {1, 1}}], 
        Line[{{0, 0}, {-1, 1}}]}]

Utilizzando la funzione Nest siamo in grado di applicare ripetutamente doTwins ad un oggetto iniziale. Visualizzando ad esempio gli oggetti grafici ottenuti partendo dal segmento di estremi (0, 0) e (1, 1) e applicando la funzione due volte, otteniamo:

Show[
        Graphics[Nest[doTwins, {Line[{{0, 0}, {0, 1}}]}, 2]]];

Modificate il codice contenuto nella cella precedente in modo che visualizzi l'intero "albero" e non solo gli ultimi "rami" (suggerimento: usate la funzione NestList).

soluzione

Show[
        Graphics[
          NestList[doTwins, {Line[{{0, 0}, {0, 1}}]}, 2]]];

Verificate quali opzioni di Show possano essere modificate in modo da migliorare l'output grafico dell'"albero".

soluzione

Show[Graphics[NestList[doTwins, {Line[{{0, 0}, {0, 1}}]}, 2]], 
        AspectRatio -> Automatic, PlotRange -> All];

Provate ora a creare un albero un po' più "adulto", aumentando il numero di chiamate a doTwins. Se tutto è andato bene, applicando la funzione sette volte dovreste ottenere il seguente risultato:

soluzione

Show[Graphics[NestList[doTwins, {Line[{{0, 0}, {0, 1}}]}, 7]], 
        AspectRatio -> Automatic, PlotRange -> All];

Rigenerate lo stesso albero usando come punto di partenza il segmento di estremi (0, 0) e (1, 1). Ripetete il procedimento scegliendo altri segmenti di partenza.

soluzione

Show[Graphics[NestList[doTwins, {Line[{{0, 0}, {1, 1}}]}, 7]], 
        AspectRatio -> Automatic, PlotRange -> All];

Sulla base del codice scritto fino ad ora, scrivete una funzione tree che, partendo dal segmento di estremi (0, 0) e (0, 1), genera un albero sulla base di quattro argomenti che rappresentano rispettivamente il numero di iterazioni di doTwins, gli angoli di rotazione, la scala ed eventuali opzioni da passare a Show.

soluzione

tree[depth_, rotationP_, shrinkageP_, opts___] := 
      Block[{}, 
rotation = 
          rotationP; 
shrinkage = 
          shrinkageP; 
Show[
          Graphics[
            NestList[
doTwins, {Line[{{0, 0}, {0, 1}}]}, 
              depth]], 
opts, 
          AspectRatio -> Automatic, 
          PlotRange -> All]
]

Dovreste ottenere i seguenti risultati:

tree[7, 0.3, 0.8];

tree[8, 0.5, 0.75];

tree[10, 0.5, 1];

Visualizzare un albero più realistico I

Modificare lo spessore dei rami

Modificate tree in modo che lo spessore dei segmenti tenda a diminuire man mano che aumenta il numero di chiamate a doTwins.

soluzione

thicknessShrinkage = 0.8;
originalThickness = 0.5;
twinLine[Thickness[th_]] := 
        Thickness[th thicknessShrinkage];

Clear[tree];
tree[depth_, rotationP_, shrinkageP_,  thicknessShrinkageP_, 
        originalThicknessP_, opts___] := 
      Block[{}, 
rotation = 
          rotationP; 
shrinkage = 
          shrinkageP; 
thicknessShrinkage = 
          thicknessShrinkageP; 
originalThickness = 
          originalThicknessP; 
Show[
          Graphics[
            NestList[
doTwins, {Thickness[
                  originalThickness], Line[{{0, 0}, {0, 1}}]}, 
              depth
]], 
opts, 
          AspectRatio -> Automatic, 
          PlotRange -> All]
]

Dovreste ottenere i seguenti risultati:

tree[6, 0.4, 0.9, 0.75, 0.05];

tree[7, 0.5,  .9, 0.75, 0.05];

Effettuate delle chiamate successive a tree aumentando progressivamente l'argomento che indica il numero di chiamate ricorsive (in altre parole, il numero dei "rami" dell'albero). Raggruppate le celle di output che ottenete e visualizzate un'animazione che mostra come l'albero "cresce".

soluzione

Table[
        tree[7, r, 0.5 + r, 0.75, 0.005 + 0.07  r, 
          PlotRange -> {{-6, 6}, {0, 8}}], {r, 0, 0.5, 
          0.02}];

Visualizzare un albero più realistico II

Non utilizzare valori fissi per i parametri delle trasformazioni

Modificare le funzioni definite fino ad ora in modo da introdurre delle variazioni casuali nei parametri di volta in volta utilizzati per ruotare e/o scalare i rami dell'albero, in modo da ottenere un output più realistico. Di seguito è riportata una delle possibili soluzioni, in cui tutti i parametri che regolano la generazione degli alberi sono gestiti mediante il meccanismo delle opzioni:

Clear[rotateLine];
rotateLine[Line[{start_, end_}], angle_] := 
        Line[{end, 
            end + branchShortening*
                rotate2D[end - start, 
                  angle Random[Real, {0.5, 1.5}]]}];

Clear[twinLine];
twinLine[Line[points_]] := {rotateLine[Line[points], branchRotation], 
          rotateLine[Line[points], -branchRotation]};

twinLine[Thickness[th_]] := 
        Thickness[th branchThinning];

twinLine[
          RGBColor[r_, g_, b_]] := (branchColors = 
            RotateLeft[branchColors]; 
First[
            branchColors]
);

Clear[doTwins];
doTwins[lines_] := Flatten[Map[twinLine, lines]];

Options[BeautifulTree] = {Generations -> 10, 
          BranchRotation -> 0.65, BranchShortening -> 0.75, 
          BranchThinning -> 0.7, OriginalThickness -> 0.07, 
          BranchColors -> {RGBColor[0, 0, 0]}};

BeautifulTree[opts___] := 
        Block[{generations, branchRotation, branchShortening, branchThinning, 
            originalThickness, 
            branchColors}, 
{generations, branchRotation, 
              branchShortening, branchThinning, originalThickness, 
              branchColors} = {Generations, BranchRotation, 
                  BranchShortening, BranchThinning, OriginalThickness, 
                  BranchColors} /. {opts} /. 
              Options[BeautifulTree]; 
Show[
            Graphics[
              NestList[
doTwins, {First[branchColors], 
                  Thickness[originalThickness], Line[{{0, 0}, {0, 1}}]}, 
                generations]], 
FilterOptions[Show, opts], 
            AspectRatio -> Automatic, 
            PlotRange -> 
              All]
];
FilterOptions[command_Symbol, opts___] := 
        Block[{keywords = 
              First  /@  Options[command]}, 
Sequence @@ 
            Select[{opts}, 
              MemberQ[keywords, 
                  First[#]] &]
];

Utilizzando le funzioni sopra definite si possono ottenere grafici come quelli che seguono. Va rimarcato il fatto che anche eseguendo lo stesso codice l'output sarà di volta in volta leggermente differente, a causa dell'introduzione di variazioni casuali dei parametri.

BeautifulTree[
Generations -> 
          10, 
BranchRotation -> 
          0.65, 
BranchShortening -> 
          0.75, 
BranchThinning -> 
          0.7, 
OriginalThickness -> 
          0.07];

BeautifulTree[];

BeautifulTree[BranchRotation -> 0.2];

Visualizzare un albero più realistico III

Aggiungere un tocco di colore

L'esempio riportato nella sezione precedente implementa anche l'opzione BranchColors che permette di specificare una lista di colori da applicare ai vari livelli dei rami presenti in un albero.

BeautifulTree[
        BranchColors -> {
RGBColor[0.562, 0.236, 
              0.071], 
RGBColor[0.547, 0.229, 
              0.069], 
RGBColor[0.500, 0.210, 
              0.063], 
RGBColor[0.469, 0.196, 
              0.059], 
RGBColor[0.033, 0.281, 
              0.035], 
RGBColor[0.046, 0.395, 
              0.050], 
RGBColor[0.055, 0.469, 
              0.059], 
RGBColor[0.070, 0.602, 
              0.076], 
RGBColor[0.085, 0.727, 
              0.092], 
RGBColor[0.109, 0.937, 
              0.118], 
RGBColor[0.013, 0.750, 
              0.028], 
}];

Visualizzare un albero più realistico IV

Gli alberi veri sono in tre dimensioni

I più bravi possono costruire degli alberi tridimensionali modificando opportunamente le funzioni descritte precedentemente e sfruttando le istruzioni di Mathematica che restituiscono grafici in tre dimensioni.