• Non ci sono risultati.

Fase 1: Sviluppi analitici

Di seguito vengono definite le funzioni necessarie in questa fase. La prima è una funzione di bisezione, utilizzata per trovare gli zeri di una funzione. In particolare, verrà utilizzata per ricercare gli zeri dei determinanti associati ai vari sistemi differenziali che rappresentano le catene. Poiché il calcolo di questi determinanti richiede molto spes- so la valutazione di funzioni iperboliche, il cui valore può diventare rapidamente molto elevato, è stato scelto di implementare un metodo di ricerca degli zeri il cui funzionamento dipendesse il meno possibile dal valore assoluto della funzione da azzerare. La preferenza è ricadu- ta sulla bisezione semplice, arrestata quando l’intervallo di ricerca si restringe oltre una certa soglia, perché è necessario conoscere solo il segno della funzione da azzerare.

1 def bisezione(f, ranger, toll=1e-10): 2 """

3 Prende in ingresso una funzione f e due estremi tra i 4 quali cercarne uno zero. Restituisce lo zero, cercato 5 per bisezione semplice.

6 *f* = funzione da azzerare;

7 *ranger* = lista [estremo sinistro, estremo destro]; 8 *toll* = distanza tra i due estremi alla quale 9 interrompere il calcolo.

10 """

11 x1 = ranger[0] 12 x2 = ranger[1] 13

14 #Controllo che i due estremi forniti siano a cavallo 15 #di uno zero

16 if f(x1)*f(x2) > 0:

17 return 'errore 0: i due estremi hanno lo stesso segno' 18

19 xc = (x2 + x1)*0.5 20

21 #La funzione è definita in maniera ricorsiva, e 22 #si interrompe quando i due estremi sono a 23 #distanza <= toll

24 if x2-xc > toll:

25 if f(xc)*f(x1) > 0:

26 xc = bisezione(f, ranger=[xc, x2], toll=toll)

27 else:

28 xc = bisezione(f, ranger=[x1, xc], toll=toll) 29 return xc

Per poter usare la funzione di bisezione è necessario scegliere accu- ratamente gli estremi tra i quali cercare gli zeri. Per farlo, la funzione

cercaEstremi scorre la funzione da azzerare calcolandola in due va-

lori dell’ascissa a piccola distanza tra di loro, e si arresta quando la funzioneassume valori di segno diverso nei due punti.

Una seconda funzione, listaEstremi, invoca cercaEstremi più volte fino a trovare un certo numero di intervalli in cui è presente uno zero, e crea una lista con questi intervalli che potrà poi essere usata dalla funzione bisezione.

1 def cercaEstremi(f, extr=[0.1,0.1], passo=1, xmax=20000): 2 """

3 Restituisce un intervallo nel quale è contenuto uno zero 4 della funzione f(x), sotto forma di lista dei due estremi. 5 *f* = funzione da azzerare;

6 *extr* = estremi dell'intervallo di primo tentativo; 7 *passo* = ampiezza dell'intervallo;

9 (per troncare loops). 10 """

11 x1 = extr[0] 12 x2 = extr[1] 13

14 #Controlla che il procedimento non si sia spinto oltre 15 xmax a cercare 16 if x1 > xmax: 17 return "errore 1" 18 19 while f(x1)*f(x2) > 0: 20 x1 = x2 21 x2 = x2 + passo 22 23 return [x1, x2] 24

25 def listaEstremi(f, lista, x0=0.1, passo=1, num=10): 26 pax = passo

27 """

28 Restituisce una lista di liste, ognuna delle quali 29 corrisponde ad un intervallo nel quale è presente uno 30 zero della funzione f(x).

31 *f* = funzione da azzerare;

32 *lista* = lista vuota che verrà riempita e restituita; 33 *x0* = valore iniziale per la funzione cercaEstremi; 34 *passo* = ampiezza degli intervalli;

35 *num* = numero di intervalli da ricercare. 36 """

37 if len(lista) < num:

38 lista.append(cercaEstremi(f, extr=[x0,x0],

39 passo=pax))

40 listaEstremi(f, lista, x0=lista[-1][1], passo=pax,

41 num=num)

42 return lista

Infine, la funzione listaZeri, che richiama tutte quelle precedente- mente definite e costruisce una lista di zeri per la funzione che le viene passata.

1 def listaZeri(f, start=0.1, passo=1, modi=10): 2 pax = passo

3 """

4 Restituisce una lista di zeri della funzione f(x), 5 ricercati con la funzione bisezione all'interno degli 6 intervalli calcolati dalla funzione listaEstremi. 7 *start* = valore di x dal quale iniziare la ricerca;

8 *modi* = numero di zeri da ricercare. 9 """

10 lista_estremi = listaEstremi(f, [], x0=start, passo=pax,

11 num=modi)

12 lista_zeri = [bisezione(f, ranger=extr)

13 for extr in lista_estremi]

14

15 return lista_zeri

8.2.2 Calcolo, costruzione e salvataggio delle tabelle per travi con diverse condizioni di vincolo

Lo scopo di questa prima fase è, in generale, quello di risolvere il problema differenziale legato ad una trave tesa con condizioni di vincolo incognite che vibra liberamente. La soluzione che ci si aspetta è nella forma (tiro, vincoli, frequenze naturali).

È possibile ottenere queste soluzioni tramite il determinante della matrice associata al sistema differenziale che descrive la trave. Que- sto sarà sempre una funzione delle incognite del problema, e grazie al programma è possibile fissare il valore di tutte queste incognite meno una, calcolare quella rimanente per bisezione, e registrare la così ottenuta soluzione. Variando i valori che si fanno assumere alle variabili controllate manualmente è possibile creare un database con indicazioni sul comportamento della trave per condizioni di vincolo e di tiro diverse. La grande quantità di dati generata viene immagazzi- nata in strutture dette arrays, che essendo per natura estendibili a n dimensioni accolgono facilmente soluzioni come le nostre.

Per rendere la trattazione teorica ed il programma adattabili a qualsiasi trave, tutte le variabili geometriche e meccaniche che influen- zano il problema sono state adimensionalizzate, riconducendosi alle seguenti grandezze parametriche:

α(N) = NL 2 2EJ β 2 i(fi) = ρA EJω 2 iL4 θ = kL EJ (8.1)

Questi valori, ottenuti per così dire in modo diretto (da una descri- zione della trave ai valori delle sue frequenze naturali) servono per poter poi affrontare il problema inverso: come calcolare tiro e grado di vincolo di una trave -o di una catena- di cui si conoscono le prime x frequenze naturali di vibrazione. La soluzione viene ricercata per minimizzazione, cioè riducendo il più possibile una data funzione di errore che misura la differenza tra le frequenze misurate e quelle di una trave teorica con un dato tiro e con date condizioni di vincolo. È un procedimento iterativo che in genere richiede di valutare molte vol- te il comportamento di travi teoriche diverse. Avendo a disposizione le tabelle (che vengono calcolate una volta sola), non è necessario per

il programma ricalcolare le frequenze della trave teorica a partire dai problemi differenziali: gli è sufficiente interpolare tra i valori già noti. Il risparmio, in tempo di calcolo, è enorme.

Per seguire lo sviluppo teorico del problema, e per avere un confron- to con casi estremi, prima di trattare il caso più generale di una trave tesa con vincoli ignoti alle estremità (ed eventualmente una massa aggiunta in posizione nota), è utile studiare casi notevoli, più semplici perché dipendenti da un numero inferiore di parametri.

Trave semplicemente appoggiata

Nella trave semplicemente appoggiata il determinante dipende esclu- sivamente dal tiro. Sono ignoti α e βi. Viene fissato α nel range [0, 500], e vengono calcolati i valori di β corrispondenti ai primi 10 modi di vibrare della trave, quindi β1,..., β10.

1 def detApp(a, b, tolleranza=1e-5):

2 L = np.sqrt(-a + np.sqrt(a**2 + b**2)) 3 M = np.sqrt(a + np.sqrt(a**2 + b**2)) 4 CL = np.cos(L) 5 SL = np.sin(L) 6 ChM = np.cosh(M) 7 ShM = np.sinh(M) 8 Sapp = np.array([[1, 0, 1, 0], 9 [-L**2, 0, M**2, 0], 10 [CL, SL, ChM, ShM], 11 [-CL*L**2, -SL*L**2, ChM*M**2, ShM*M**2]]) 12 return np.linalg.det(Sapp)

Qui di seguito vengono:

• definito un array (arr_app) con i valori di beta per alpha in (0,500) e per 10 modi;

• salvato tale array nel file "dati_ottimizzazione.hdf5" come ogget- to ’appoggiata’;

• salvato tale array in un file csv per trasformarlo in tabella. La possibilità di esportare la tabella in formato csv si esaurisce fonda- mentalmente con la trave semplicemente appoggiata e quella doppia- mente incastrata, essendo gli unici due casi dove i parametri in gioco sono solo 2. Può comunque essere utile avere a disposizione queste tabelle per confronti veloci sul campo, così come può essere utile stampare tabelle analoghe per la trave con vincoli elastici simmetrici alle estremità, considerando alcuni valori notevoli dei vincoli.

1 arr_app = np.array([listaZeri(lambda x: detApp(float(a), x)) 2 for a in range(0, 500, 10)])

3

4 with h5py.File('dati_ottimizzazione.hdf5') as failozzo: 5 h5_app = failozzo.create_dataset("tabelle/appoggiata",

6 data=arr_app)

7

8 np.savetxt("tabelle/appoggiata.csv", arr_app, delimiter=',', 9 fmt='%.3f')

Trave incastrata

La dipendenza è nuovamente solo dal tiro. Ignoti α e βi. Viene fissato αnel range [0, 500], e vengono calcolati i valori di β corrispondenti ai primi 10 modi di vibrare della trave, quindi β1,..., β10.

1 def detInc(a, b, tolleranza=1e-5):

2 L = np.sqrt(-a + np.sqrt(a**2 + b**2))/2 3 M = np.sqrt(a + np.sqrt(a**2 + b**2))/2

4 R1 = M * np.cos(L) * np.tanh(M) + L * np.sin(L) 5 R2 = M * np.sin(L) - L * np.cos(L) * np.tanh(M) 6 return R1*R2

Qui di seguito vengono:

• definito un array (arr_inc) con i valori di beta per alpha in (0,500) e per 10 modi;

• salvato tale array nel file "dati_ottimizzazione.hdf5" come ogget- to ’incastrata’;

• salvato tale array in un file csv per trasformarlo in tabella.

1 arr_inc = np.array([listaZeri(lambda x: detInc(float(a), x)) 2 for a in range(0, 500, 10)])

3

4 with h5py.File('dati_ottimizzazione.hdf5') as failozzo: 5 h5_inc = failozzo.create_dataset("tabelle/incastrata",

6 data=arr_inc)

7

8 np.savetxt("tabelle/incastrata.csv", arr_inc, delimiter=',', 9 fmt='%.3f')

Trave con cerniere elastiche simmetriche

Rispetto ai casi precedenti, viene introdotta una nuova incognita θ (TH nel codice) che serve a rappresentare il grado di vincolo alle estremità della trave. Vengono fissati α nel range [0, 500] e θ nel range [0, 1000] e vengono calcolati i valori di β corrispondenti ai primi 10 modi di vibrare della trave, quindi β1,..., β10.

1 def detEl(TH, a, b):

2 L = np.sqrt(-a + np.sqrt(a**2 + b**2))/2 3 M = np.sqrt(a + np.sqrt(a**2 + b**2))/2

4 F1 = 2*(L**2+M**2)*np.cos(L) + TH*L*np.sin(L)

5 F2 = TH * M * np.cos(L) * np.tanh(M)

6 F3 = np.tanh(M)*(2*(L**2+M**2)*np.sin(L) - TH*L*np.cos(L)) 7 F4 = TH * M * np.sin(L)

8 R1 = F1 + F2 9 R2 = F3 + F4 10 return R1*R2

Qui di seguito vengono:

• definito un array multidimensionale (arr_el) con i valori di beta per alpha in (0,500,1), TH in (0,1000,1), e per 10 modi;

• salvato tale array nel file "dati_ottimizzazione.hdf5" come ogget- to ’molle_simmetriche’.

1 arr_el = np.array([[listaZeri(lambda x: detEl(TH, float(a), x))

2 for a in np.arange(0., 500., 1)]

3 for TH in np.arange(0., 1000., 1)])

4

5 with h5py.File('dati_ottimizzazione.hdf5') as failozzo:

6 h5_el = failozzo.create_dataset("tabelle/molle_simmetriche",

7 data=arr_el)

Trave con cerniere elastiche asimmetriche

Rispetto al caso simmetrico si introduce come ulteriore incognita γ (GA nel codice), che esprime l’asimmetria tra i vincoli. Nella pratica, si fa conto che le due molle abbiano costanti elastiche (normalizzate rispetto ad EJ/L) pari a θ(1 + γ) e θ(1 + γ).

1 def detAsim(TH, GA, a, b):

2 L = np.sqrt(-a + np.sqrt(a**2 + b**2)) 3 M = np.sqrt(a + np.sqrt(a**2 + b**2))

4 CL = np.cos(L/2) 5 SL = np.sin(L/2) 6 CHM = np.cosh(M/2) 7 SHM = np.sinh(M/2) 8 Sasim = np.array([[CL, 0, CHM, 0], 9 [SL*L*TH + CL*L**2, GA*CL*L*TH, 10 -SHM*M*TH-CHM*M**2, GA*CHM*M*TH], 11 [0, SL, 0, SHM], 12 [SL*L*TH, GA*CL*L*TH-SL*L**2, 13 -SHM*M*TH, GA*CHM*M*TH+SHM*M**2]]) 14 return np.linalg.det(Sasim)

8.3 fase 2: stima del tiro (soluzione del problema inver-

Documenti correlati