PHP Matematik formel "solve" funktion

Tags:    php udregning

Hej alle!

Jeg forsøger at lave et matematik "tool" i php, hertil skal jeg brugen en funktion som jeg kunne give en formel samt en ubekendt jeg ønsker at isolere (samme princip som ti-89 lommeregneren) fx.

Ohms lov
U = R * I

Nu ønsker jeg at isolere "I" ved hjælp af php:

Fold kodeboks ind/udPHP kode 


Funktionen solve skal så lave et return med med formlen: I = U / R

Dette er blot et hurtigt eksempel på hvordan den skal virke jeg ønsker naturligvis at den skal fungere med meget mere komplekse formler.

Håber der er en eller flere herinde der kan hjælpe med at løse dette.
På forhånd tak!



Indlæg senest redigeret d. 25.02.2015 16:21 af Bruger #16751
3 svar postet i denne tråd vises herunder
2 indlæg har modtaget i alt 13 karma
Sorter efter stemmer Sorter efter dato
Nu har Kevin svaret siden jeg begyndte på mit længere svar, men siden jeg nu har brugt tid på det:

Her er mit bud på en løsning som består af to dele.

Den (relativt) nemme del:

1. Parse dit udtryk til et AST (Abstract Syntax Tree). Dette er et træ som beskriver din ligning. Det er ikke nok bare at læse dit udtryk som en liste bestående af TAL/VARIABEL efterfulgt af en OPERATOR efterfulgt af en TAL/VARIABEL igen. Ellers kan du risikere at parse 5 + 3 * 4 som (5 + 3) * 4, hvilket jo er forkert. En liste kan ikke fortælle dig at * binder stærkere end +, men det kan en træstruktur.

Og den svære del. Hvor svær denne del er afhænger af hvor komplekse dine udtryk er. Hvis du holder dig til en enkelt ukendte variabel, som kun er et sted, og holder dig til simpel matematik, herunder, eventuelt brug af avancerede matematiske funktioner som log, potens, rødder bør det være praktisk muligt. Hvis du går udover dette, med f.eks. integration, differentialligninger, eller flere ukendte, eller bare en enkelt ukendt flere steder i udtrykket, kan det blive meget mere komplekst. Husk det er heller ikke alle ligninger, man kender en analytisk løsning til endnu (eller måske nogensinde). Hvis du kan finde et andet bibliotek der kan omskrive ligningen for at isolere variablen for dig (måske et skrevet i C/C++ du kan wrappe som et PHP module), vil jeg stærkt anbefale dig dette.

2. Brug "rewrite-rules" (omskrivningsregler) til at omskrive dit udtryk med henblik på at isolere den ukendte variabel på den ene side af lighedstegnet. Kan eventuelt simplificere andre udtryk undervejs. F.eks i stedet for at løse for Y: X = (2+3) * Y ==> Y = X / (2+3), laver du først ==> X = 5 * Y og så ==> Y = X / 5.

For at parse udtrykket i PHP anbefaler jeg dig enten en såkaldt recursive descent parser, eller at du bruger en parser generator som f.eks.: https://github.com/hafriedlander/php-peg , hvor du beskriver hvordan dine udtryk er formet og så kan den læse noget tekst og give dig et AST som output.

Et AST er en træstruktur bestående af knuder. Hver knude indikerer et matematisk udtryk som kan bestå af underudtryk. Lad os tage et eksempel, hvor vi gerne vil løse for A: Z = (A + 4)^2 * Y

Dette bliver parset til et AST der ligner:

Billede 1

For at simplificere sådan et AST evaluerer vi udtrykket fra bunden af og op. Lad og sige at A var kendt og vi ønskede at finde Z. Så ville vi evaluere højre side af lighedstegnet. Dette er en produktknude hvilket betyder vi skal gange dens to underudtryk. Men de skal måske evalueres først. Lad os sige vi valgte højre side af multiplikationen først, nemlig Y. Hvad er Y for en knude? Det er en "variabel" knude, (og siden vi antager at alle variabler er kendte undtagen lige 'Z'), så slår vi bare op i en tabel og erstatter den med 7, fordi vi siger lige at Y = 7. Nu har vi løst højre side af multiplikationen, men mangler stadig venstre side. Det er en Pow(2), dvs. opløftet i 2. Den kan vi simplificere ved at erstatte den med en multiplikationen af dens underudtryk 2 gange. Nu står vi med:

Billede 2

Vi evaluerer den højre side. Den er en "tal-knude", så den evaluerer bare til sin egen værdi. Så tager vi venstre side, det er en "variabel" knude, som vi slår op. Lad os sige at A = 8. Nu har vi reduceret begge underudtryk til vores plus-knude, så vi lægger bare tallene sammen og den får værdien 12. Så løser vi den anden plus-knude under den nederste multiplikation. Den giver selvfølgelig også 12. Nu kender vi begge underudtryk til multiplikations-knuden, nemlig 12, og 12 og vi ganger dem og det giver 144. Nu kender vi begge underudtryk til den øverste multiplikations-knude som er 144 og 7. Dem ganger vi og får 1008. Nu ved vi at Z = 1008. Dette er det generelle princip for hvordan en interpreter virker.

Lad os vendte tilbage til problemet hvor vi vil løse A og kender de andre. Her kan vi ikke bruge samme trick, fordi vi har ikke isoleret A. For at gøre det skal vi bruge lignende, men mere komplekse omskrivnings- og reduceringsregler.

Lad os starte i toppen igen. Vi har måske en omskrivningsregel der siger at for en multiplikationsknude, hvor den ukendte (A), forekommer i det ene underudtryk men ikke i det andet, så dividerer vi begge sider af lighedstegnet med det andet. Det betyder vi fjerner multiplikations-udtrykket for højreside, og tilføjet et divisions-udtryk på venstre side:

Billede 3

For at reducere Pow(2), tager vi kvadrat roden af begge sider:

Billede 4

Det modsatte af plus er minus, så vi trækker 4 fra begge sider:

Billede 5

Nu har du isoleret A. For at finde svaret evaluerer du den anden side af lighedstegnet med metoden jeg beskrev lige før. Jo flere omskrivningsregler du kan lave desto flere ligninger kan dit system løse.



Indlæg senest redigeret d. 26.02.2015 17:22 af Bruger #14645
Dette kan godt lade sig gøre, det er dog et større arbejde..

Først skal du formå at parse din input tekst om til symboler, med deres tilhørende koefficienter, og her har du så din løsning, da du jo så efterfølgende ville kunne ændre på din ligning som det passer dig.

Hvis du skal løse flere ligninger med flere ubekendte skal du formentlig benytte dig af Gauss Jordan eliminations algoritmen. Her kan du jo evt. benytte dig af et bibliotek i stil med dette http://pecl.php.net/package/lapack/0.1.0.


Som sagt ikke en umulig opgave, men en der tager lidt tid :)



Af hvad jeg ved af, kan det ikke lade sig gøre - I hvert fald ikke med php



t