Fråga:
Effektiv algoritm / datastruktur för att beräkna glidande medelvärden
Cybergibbons
2014-03-07 14:32:44 UTC
view on stackexchange narkive permalink

För närvarande utvecklar jag ett grafiskt LCD-system för att visa temperaturer, flöden, spänningar, effekt och energi i ett värmepumpsystem. Användningen av en grafisk LCD betyder att hälften av min SRAM och ~ 75% av min blixt har använts av en skärmbuffert och strängar.

Jag visar för närvarande min / max / genomsnittliga siffror för energi At midnatt när den dagliga siffran återställs, kontrollerar systemet om konsumtionen för dagen är över eller under det tidigare lägsta eller högsta och lagrar värdet. Genomsnittet beräknas genom att dividera den kumulativa energiförbrukningen med antalet dagar.

Jag skulle vilja visa det dagliga genomsnittet under den senaste veckan och månaden (4 veckor för enkelhetens skull) dvs. ett rullande genomsnitt. För närvarande handlar det om att upprätthålla en uppsättning värden under de senaste 28 dagarna och beräkna ett genomsnitt över hela matrisen för månadsvis och de sista 7 dagarna för veckovisa. energi är i formen "12.12kWh"), men detta använde 28 * 4 byte = 112 byte (5,4% av SRAM). Jag har inte något emot att bara ha en enda decimalpunkt för upplösning, så jag bytte till att använda uint16_t och multiplicera siffran med 100. Detta innebär att 12.12 representeras som 1212, och jag delar med 100 för visningsändamål.

Storleken på matrisen är nu nere till 56 byte (mycket bättre!).

Det finns inget trivialt sätt att minska siffran till en uint8_t som jag kan se. Jag kunde tolerera förlusten av en decimal ("12.1kWh" istället för "12.12kWh"), men förbrukningen är ofta högre än 25,5kWh (255 är det högsta värdet som representeras av ett 8-bitars osignerat heltal). Förbrukningen har aldrig varit under 10,0 kWh eller över 35,0 kWh, så tänkbart skulle jag kunna subtrahera 10 från de lagrade siffrorna, men jag vet att vi en dag kommer att överskrida dessa gränser.

Jag testade sedan kod för att packa 9-bitars värden i en matris. Detta ger ett intervall på 0-51.2kWh och använder totalt 32 byte. Att komma åt en sådan matris är dock ganska långsam, speciellt när du måste iterera över alla värden för att beräkna ett genomsnitt.

Så min fråga är - finns det ett mer effektivt sätt att beräkna ett glidande medelvärde med tre windows - livstid, 28 dagar och 7 dagar? Effektivitet betyder mindre när det gäller SRAM-användning, men utan att betala för enorm kod. Kan jag undvika att lagra alla värden?

Vill du beräkna ett glidande medelvärde över specifika fönster eller skulle en uppskattning / approximation av genomsnittet göra?
Jag vill ha ett glidande medelvärde under ett fönster på 7 dagar och 28 dagar.
du kan använda en upplösning på 0,2kWh (dela och multiplicera med faktor 5) och du får fortfarande 0-51,2kWh-intervall i 8 bitar
Du kan sluta sätta strängar och andra konstanter i externt RAM eller extern Flash - se ["Vad kan jag göra om jag har slut på Flash-minne eller SRAM?"] (Http://arduino.stackexchange.com/questions/221 / vad-kan-jag-göra-om-jag-går-ur-flash-minne-eller-sram).
Fem svar:
ratchet freak
2014-03-07 15:15:21 UTC
view on stackexchange narkive permalink

du kan använda en annan metod, du behåller det aktuella genomsnittet och gör sedan

  genomsnitt = (vikt1 * genomsnitt + vikt2 * nytt_värde) / (vikt1 + vikt2);  

det är inte ett riktigt rullande medelvärde och har olika semantik, men det kan ändå passa dina behov

för en mer effektiv beräkningsmetod för dina 9 bitar per lösning kan du behålla 8 högsta bitar av värdena i en matris och separera de minst signifikanta bitarna:

  uint8_t [28] highbits; uint32_t lowbits;  

för att ställa in ett värde du måste dela upp det

  void getvalue (uint8_t index, uint16_t value) {highbits [index] = value>>1; uint32_t flagga = (värde & 1) <<index; highbits | = flagga; highbits& = ~ flagga;}  

vilket resulterar i 2 skift en AND och en OR och en inte

för att beräkna genomsnittet kan du använda olika bit tricks för att påskynda det :

  uint16_t getAverage () {uint16_t sum = 0; för (uint8_t i = 0; i<28; i ++) {sum + = highbits [i]; } sum<< = 1; // multiplicera med 2 efter slingans summa + = bitantal (lowbits); retursumma / 28;}  

du kan använda ett effektivt parallellt bitantal för bitcount()

Kan du förklara mer hur detta skulle göra det möjligt för mig att beräkna genomsnittet på 7 och 28 dagar?
Jag har använt detta tillvägagångssätt för att utjämna bullriga analoga värden tidigare, och det var verkligen ganska effektivt. Jag behövde dock inte mycket precision, eftersom de resulterande värdena sattes genom en mycket grov kvantiserare. Jag behövde inte heller historiska medelvärden.
Detta gör det inte möjligt att beräkna genomsnittet för ett visst fönster.
@Cybergibbons du kan använda olika vikter för att approximera fönstret så att gamla värden blir obetydliga tidigare eller senare, eller behålla 7 dagar för 7-dagarsfönstret och detta glidande medelvärde för 28-dagars genomsnittet
asheeshr
2014-03-07 15:37:35 UTC
view on stackexchange narkive permalink

Om dina data har låg standardavvikelse skulle en metod vara att summera värden över fönstret och sedan fortsätta subtrahera medelvärdet från summan samtidigt som du lägger till det nya värdet.

Detta skulle fungera bra om det inte finns inga avvikelser , vilket leder till att det sammanlagda felet tenderar till noll över tiden.

  // Pseudokodräkning = 0 medan nyläsning och count<7: sum + = new_reading // Beräkna summan av de första 7 värdena räknas ++ medan ny läsning: // Loop till nya avläsningar tillgängliga avg = summa / 7 // Beräkna genomsnittlig summa - = avg // Subtrahera genomsnitt från summasumma + = new_reading // Lägg till nästa avläsning till summan skriv avg  
jippie
2014-03-08 00:00:57 UTC
view on stackexchange narkive permalink

Vad sägs om att bara lagra skillnaden från det tidigare värdet? Inom elektronik finns ett liknande koncept som kallas Delta Sigma-omvandlare, som används för DA / AD-omvandlare. Det förlitar sig på det faktum att den tidigare mätningen är rimligt nära den nuvarande.

En annan intressant idé. Tyvärr är jag inte säker på att energiförbrukningen alltid kommer att vara så här, eftersom det är ett värmepumpsystem och en dag kan ta 30kWh, nästa 10kWh. Jag måste verkligen samla in data och se.
Aditya Somani
2014-03-08 07:33:30 UTC
view on stackexchange narkive permalink

Varför kunde du inte bara lägga till värdena så snart du fick dem. Så vad jag menar är att du får värdet för dag 1, du delar det med 1 och lagrar det och 1 någonstans. Sedan multiplicerar du 1 med värdet och lägger till det till nästa värde och delar dem båda med 2.

Att göra den här metoden skulle skapa ett rullande medelvärde med två eller tre variabler som jag kan tänka mig. Jag skulle skriva lite kod men jag är ny på stackexchange så snälla ha med mig.

Jag förstår inte hur detta hanterar fönstret 7 dagar och 28 dagar?
Håll koll på föregående och nästa värden och fortsätt att lägga till och subtrahera dem från ditt löpande genomsnitt ...
Så då är jag tillbaka i det tillståndet att jag behöver komma ihåg 27 dagars historia, säkert?
Jag har tänkt och du har rätt. Så tekniskt gör mitt svar felaktigt. Jag investerar lite mer tid och tålamod i det. Kanske något ur lådan. Jag meddelar dig om jag kommer på något. Vi gör något så här mycket på min arbetsplats. Låt mig fråga omkring. Ledsen för förvirringen.
David Cary
2014-11-23 11:57:43 UTC
view on stackexchange narkive permalink

finns det ett mer effektivt sätt att beräkna ett glidande medelvärde med ... 28 dagar och 7 dagar? ... behöver komma ihåg 27 dagars historia ...?

Du kan komma tillräckligt nära lagring av 11 värden snarare än 28 värden, kanske något som:

  // oproverad kod // statiska variabler // kanske i enheter på 0,01 kWh? uint16_t vecka_energi [4]; // kanske i enheter på 0,1 kWh? void print_week_status () {Serial.print (F ("förra veckans totala energi:")); Serial.println (vecka_energi [0]); int summa = 0; för (int i = 0; i<4; i ++) {sum + = vecka_energi [i]; }; Serial.print (F ("Total energi under de senaste fyra fullständiga veckorna:")); Serial.println (summa); int genomsnitt_vecka_energi = summa / 4; int genomsnittlig_energi = genomsnittlig_energi / 7; Serial.print (F ("Genomsnittlig daglig energi de senaste 4 veckorna:")); Serial.println (average_daily_energy);} ogiltig print_day_status () {Serial.print (F ("gårdagens energi:")); Serial.println (daglig_energi [0]); Serial.print (F ("genomsnittlig daglig energi under de senaste sju hela dagarna:")); int summa = 0; för (int i = 0; i<7; i ++) {sum + = daglig_energi [i]; }; int genomsnitt = summa / 7; Serial.println (genomsnitt);}  

Med andra ord, snarare än att lagra varje detalj varje dag under de senaste 27 dagarna, (a) lagra 7 eller så värden med detaljerad daglig information för de senaste sju dagarna, och (b) lagrar fyra eller så "sammanfattade" värden för total eller genomsnittlig information för var och en av de senaste fyra eller så veckorna.



Denna fråga och svar översattes automatiskt från det engelska språket.Det ursprungliga innehållet finns tillgängligt på stackexchange, vilket vi tackar för cc by-sa 3.0-licensen som det distribueras under.
Loading...