C/C++ symbolische Mathematik beibringen...



  • Ich habe gestern Abend und gestern Nacht diesen Schritt gewagt, und alles
    neu gemacht 😉

    Du kannst dir diesen Schritt, nach der Pizza, sparen. Ich werde es dir
    erklären 👍

    Der Code ist total einfach 💡

    Wichtig:

    • a) Vergiss mal Java, C++ etc. Das hier ist auf einer ganz anderen Ebene!
    • b) Wir teilen dem Programm nur Fakten mit. Alles andere, wie weitere
      Rückschlüße, zieht das Programm alleine. Wir müssen nicht jede Kombination
      von Regel(n) niederschreiben.

    Code:
    d(X,X,1).

    Bedeutet, dass wenn du ein beliebiges X (ist nicht die Variable x, sondern
    irgendwas), nach X ableiten willst, kommt immer 1 heraus.

    Also x nach x ableiten, etc.

    Code:
    d(C,X,0) :- atomic(C), C \= X.

    C ist deine gegebene Gleichung, X deine gewünschte Variable. Wenn die
    Gleichung nur eine Konstante enthält (das prüft man mit atomic()), und
    C ungleich X ist, ist dass Ergebnis 0. Ungleichheit wird also mit \=
    bezeichnet.

    Code:
    d(pot(U,N),X,N*pot(U,N1)*DU) :- N>1, N1 is N-1, d(U,X,DU).

    pot(U,N) soll U^N sein. Wenn wir also U^N ableiten wollen, über X, so lautet
    das Ergebnis N*pot(U,N1)*DU ... WENN:
    a) N>1
    b) N-1 berechnet, und N1 zugewiesen wurde
    c) und d(U,X,DU). erfolgreich verlauffen ist. Diese Regel arbeitet also Rekrusiv.

    Das "," ist die UND-Verknüpfung!!! Mit ":-" definiert man die Bedingungen,
    wann die Regel auf der linken Seite gültig ist.

    Code:
    d(sin(U),X,cos(U)*DU) :- d(U,X,DU).

    Wieder das Selbe. Das Ergebnis von sin(U) (hier ist gleich die Kettenregel
    dabei!) über X abgeleitet, ist cos(U)*DU, WENN:
    Die Ableitung (Rekrusiv) von d(U,X,DU). erfolgreich verlief.

    Damit kann man das ganze Differential-Programm verstehen. Eine Sache noch:
    Das "!" ist eine Abbruchbedingt, wird Cut genannt. Damit wird der Vorgang
    abgebrochen. Damit das Programm nicht noch eine andere Regel auf das fertige
    Ergebnis anwendet.

    Ach ja: Prolog kennt kein negativen Operator... also -5 geht nicht! Man
    muss leider 0 - 5 schreiben. Leider geht also auch 5 * (-1) nicht.

    Für das Vereinfachen mache ich einen neuen Beitrag. 😉

    Nochmal zur Klarheit:
    Wir brauchen dem Programm nicht beibringen, wie man eine Konstante ableitet,
    die unter einem Bruchstrich steht etc. Denn diese Aussage fehlt im Code,
    wie viele andere auch. Aber das Programm leitet sich das selbst her. Es hat
    eine gewisse "KI" wenn man so will.

    Es weiß ja, dass gilt:
    d(C,X,0) :- atomic(C), C \= X.
    ... und diese Regel wird es nun in allen anderen Kombinationen selber anwenden.



  • Zum vereinfachen-Programm (siehe weiter oben den Code):

    Zuerst einmal, ist das "=" KEINE Zuweisung, wie in C etc. Mit dem "=" wird
    ein match geprüft, also "finde ich in dieser Variable dieses und jenes".

    Code:
    vereinfacht(T,T) :- atomic(T).

    Wenn T ein Atom ist (also nur noch ein Zeichen, eine Konstane etc., also
    kein "Sring" mehr, keine Operatoren ...) dann ist das Ergebnis der Vereinfachung
    T selbst.

    Logisch: Wenn wir zum Vereinfachen "0*x+5" geben, und irgendwann durch die
    Rekursion nur noch "5" übrig bleibt, ist der Job erledigt. Das macht diese
    Regel.

    Code:
    vereinfacht(T,V) :- T=L*R, vereinfacht(L,L1), vereinfacht(R,R1), m(L1*R1,V).

    Also gegeben ist ein Ausdruck T und das Ergebnis soll nach V. Das ist genau
    dann der Fall, wenn:

    • T=LR hat mir auch zunächst Kopfschmerzen bereitet 😉 Aber, wie schon
      gesagt, ist das "=" keine Zuweisung. T ist gegeben, klar. Nun passiert
      folgendes. Stellt euch vor, "L
      R" sei eine Maske. Nun wird versucht, ob diese
      Maske in den gegebenen Ausdruck T passt. Das ist nur dann der Fall, wenn es
      sich um eine Multiplikation handelt. Dann wird L aber auch gleich dem "Teilstring"
      zugewiesen, und auch R seinem Teil.
    • (Wieder beachten: "," ist das UND!)
      Nun werden rekrusiv die beiden "Teilstrings" L und R vereinfacht, und den
      Variablen L1 und L2 zugewiesen.
    • m() ist die Multiplikation, s() Subtraktion, a() Addition. Es werden
      also zum Schluß L1 und R1 multipliziert, und V zugewiesen.
    • Wenn das alles positiv verläuft, dann wird das V als Ergebnis zurück
      geliefert.

    Code:
    a(X+0,X) :- !.

    Wenn ein Ausdruck X + 0 bei der Addition auftaucht, dann ergibt dies immer
    (hier ohne Bedingungen) X. Und wenn das der Fall ist, wird der Vorgang sofort
    abgebrochen mit dem "!", dem Cut.

    Code:
    a(X+Y,Z) :- integer(X), integer(Y), Z is X+Y.

    X+Y ist gleich Z, wenn:
    a) X eine Zahl ist (integer prüft glaube ich nur auf natürliche Zahlen)
    b) Y eine Zahl ist
    c) die Zuweisung positiv verläuft. Hier wird Z das Ergebnis von X+Y zugewiesen
    (also ist das "=" aus Java, C und C++ das "is" in Prolog).

    Code:
    a(X+Y,X+Y). /* Default-Klausel */

    Diese default Klausel gibt es, damit alle anderen Fälle direkt durch gehen.
    Sonst würde es öfters zu einem "no" führen.

    Damit kann man sich auch dieses Programm erklären. Was meint ihr, wie lange
    es gedauert hat, dass alles heraus zu finden 🙄 Die Dokumentationen
    und eBooks zu Prolog sind sehr sehr dürftig. 😞 Und echte Bücher gibt es
    auch keine, oder nur sehr sehr sehr wenige.

    Echt schade.

    Jetzt habe ich aber eine Idee. Vielleicht kann ich cos(...) etc. mit "="
    erkennen.... 😮 Das muss ich gleich mal probieren...



  • Edit:
    Also nun hab ich die Lösung. Ich hab den Vereinfacherer mit dem Differenzierer
    vereinigt.

    Differenzierer:

    d(X,X,1).
    d(C,X,0) :- atomic(C), C \= X.
    d(pot(U,N),X,OUT) :- N>1, N1 is N-1, d(U,X,DU), simplify(N*pot(U,N1)*DU,OUT).
    d(pot(U,N),X,OUT) :- N<0, N1 is N-1, d(U,X,DU), simplify(N*pot(U,N1)*DU,OUT).
    d(sin(U),X,OUT) :- d(U,X,DU), simplify(cos(U)*DU,OUT).
    d(cos(U),X,OUT) :- d(U,X,DU), simplify(0-sin(U)*DU,OUT).
    d(exp(U),X,OUT) :- d(U,X,DU), simplify(exp(U)*DU,OUT).
    d(log(U),X,OUT) :- d(U,X,DU), simplify((1/X)*DU,OUT).
    d(U+C,X,OUT) :- atomic(C), C \= X, d(U,X,DU), simplify(DU,OUT), !.
    d(C+U,X,OUT) :- atomic(C), C \= X, d(U,X,DU), simplify(DU,OUT), !.
    d(F+G,X,OUT) :- d(F,X,DF), d(G,X,DG), simplify(DF+DG,OUT).
    d(U-C,X,OUT) :- atomic(C), C \= X, d(U,X,DU), simplify(DU,OUT), !.
    d(C-U,X,OUT) :- atomic(C), C \= X, d(U,X,DU), simplify(0-DU,OUT), !.
    d(F-G,X,OUT) :- d(F,X,DF), d(G,X,DG), simplify(DF-DG,OUT).
    d(F*G,X,OUT) :- d(F,X,DF), d(G,X,DG), simplify(DF*G+F*DG,OUT).
    d(F/G,X,OUT) :- d(F,X,DF), d(G,X,DG), simplify((G*DF-F*DG)/(G*G),OUT).
    

    "Der Vereinfacher":

    simplify(T,T) :- atomic(T) | T=sin(_) | T=cos(_) | T=pot(_,_) | T=exp(_).
    simplify(T,V) :- T=L*R, simplify(L,L1), simplify(R,R1), m(L1*R1,V).
    simplify(T,V) :- T=L+R, simplify(L,L1), simplify(R,R1), a(L1+R1,V).
    simplify(T,V) :- T=L-R, simplify(L,L1), simplify(R,R1), s(L1-R1,V).
    
    a(X+0,X) :- !.
    a(0+X,X) :- !.
    a(X+Y,Z) :- integer(X), integer(Y), Z is X+Y.
    [...]
    

    Bei dem Vereinfacher habe ich nur den Anfang neu geposted. Der Rest ist
    gleich -- siehe eine oder zwei Seiten weiter vorne.

    Erklärung:
    Nach dem differenzieren wird jedes Ergebnis (von den beiden trivalen Fällen
    mal abgesehen) an den Vereinfacher weiter geleitet.

    Code:
    simplify(T,T) :- atomic(T) | T=sin() | T=cos() | T=pot(,) | T=exp(_).

    Hier ist die einzigste richtige Neuerung zu finden. Dazu muss man wissen, dass
    das ""-Zeichen in Prolog für ALLES steht. Also bedeutet "sin()", dass hier
    ein Sinus mit irgend einem beliebigen Inhalt stehen kann / darf.

    T=sin(_) bedeutet also, dass in T nach einem Sinus mit einem unbekannten
    Argument gesucht wird. Wenn dies gefunden wurde, wird true zurück gegeben.
    Und das "|" ist das logische ODER.

    Damit wäre das vom Tisch. 😃 Ich werde das auch mal in einem Dokument
    zusammenfassen... das Ganze. Schön sauber mit LaTeX.

    Beeindruckend:
    Ich hab ja nur diese Ausnahme hinzugefügt, diese eine Zeile geändert.
    Und schwups: Er kann trotzdem Sachen wie "sin(bla bla bla) * 0 * 45054"
    erkennen, und vereinfachen -- hab ich ihm aber nicht gezeigt. Hat er es
    sich selbst hergeleitet 😮 😃

    Ich werde jetzt noch ein paar weitere Regeln zum vereinfachen definieren.
    Also das er nicht immer pot(x,1) --> x^1 schreibt, sondern eben x.
    Etc. ... und dann kann es mit den Integralen weiter gehen 😉



  • ths schrieb:

    Ich habe gestern Abend und gestern Nacht diesen Schritt gewagt, und alles
    neu gemacht 😉

    Du kannst dir diesen Schritt, nach der Pizza, sparen. Ich werde es dir
    erklären 👍

    ok, code ist klar.
    ich setze neu an, weil ich mit ein paar details einfach unzufrieden bin.
    ich bewege mich also in ne andere reichtung.
    und dabei gucke ich dir natürlich möglichst viel ab.
    und vielleicht kannste bald bei mir auch ein wenig abgucken.

    am konzept mit const halte ich fest.
    er muß ja (a*b)*x differenzieren können, und das geht nur, wenn
    er (a*b) als const bezüglich erkennt, vermute ich. ich "weiß"
    einfach, daß das richtig ist. könnte aber auch falsch sein.

    ich fasse differenzieren und vereinfachen insofern zusammen, als daß bei
    jedem differenzierschriff gleich das ergebnis vereinfacht werden soll.

    diff(X,X,1).
    diff(T,X,0) :- const(T,X).
    diff(F*G,X,V) :- diff(F,X,DF), diff(G,X,DG), simplify(DF*G+FDG,V).
    diff(F*G,X,V) :- diff(F,X,DF), diff(G,X,DG), simplify(DF*G-F
    DG,V).

    die elementaren funktionen möchte ich auch direkt ableiten, keine tricks durch
    einbettung in kettenregel.

    diff(sin(X),X,cos(X)).
    diff(cos(X),X,-sin(x)).

    const benutze ich gleich mal.
    diff(C*X,X,C) :- const(C,X).

    kann zur zeit die kettenregel nicht definieren, suche im netz nach nem vollständigen
    handbuch.



  • volkard schrieb:

    ich setze neu an, weil ich mit ein paar details einfach unzufrieden bin.

    Ist ja okay. Wir können uns ja dann auf ein Modell einigen, und die Teilprogramme
    zusammenfügen.

    ich fasse differenzieren und vereinfachen insofern zusammen, als daß bei
    jedem differenzierschriff gleich das ergebnis vereinfacht werden soll.

    Siehe meinen anderen Beitrag, den ich eben editiert habe 😃 Da hab ich das
    auch so 😉

    die elementaren funktionen möchte ich auch direkt ableiten, keine tricks durch
    einbettung in kettenregel.

    Habs nur so, weil es in dieser PDF so drinne stand. Und die meinten, es ginge
    nicht anders. Wenn du es schaffst... gut.

    kann zur zeit die kettenregel nicht definieren, suche im netz nach nem
    vollständigen handbuch.

    Wie? Du suchst die Kettenregel? Nimm mein Mathe-Dokument:
    http://www.cyberzone2001.de/documents/Mathematik.pdf

    Oder was meinst du? Oder suchst du ein Handbuch für Prolog? Dann wünsche
    ich dir viel Spaß. Ich habs aufgegeben... Die Dinger im Netz sind alle
    nicht sooo gut. Nicht so schön wie für Java und C oder C++...



  • kettenregel funktioniert!

    /*C ist konstant bezüglich X
    also C ist nicht abhängig von X
    genaue implemetierung fehlt noch*/
    const(C,X) :- atomic(C), C \= X.
    
    diff(X,X,1).
    diff(T,X,0) :- const(T,X).
    diff(C*X,X,C) :- const(C,X).
    
    diff(F*G,X,V) :- diff(F,X,DF), diff(G,X,DG), simplify(DF*G+F*DG,V).
    diff(F*G,X,V) :- diff(F,X,DF), diff(G,X,DG), simplify(DF*G-F*DG,V).
    
    diff(sin(X),X,cos(X)).
    diff(cos(X),X,-sin(x)).
    
    /*Kettenregel*/
    diff(FG,X,V) :- 
      functor(FG,F,1), 
      arg(1,FG,A), 
      diff(FG,A,DF), 
      diff(A,X,DG), 
      simplify(DF*DG,V).
    
    diff(T,x,S) :- simplify(T,S).
    
    simplify(1*X,V) :- simplify(X,V).
    simplify(X*1,V) :- simplify(X,V).
    simplify(X+X,V) :- simplify(2*X,V).
    simplify(X+X,V) :- simplify(X*2,V).
    simplify(A+B,SC) :- simplify(A,SA),simplify(B,SB),simplify(SA+SB,SC).
    simplify(X,X) :- !.
    
    :- diff(sin(2*x),x,V),print(user_output,V),nl,flush.
    :- halt.
    


  • wie vereinfachst du a*b+b*a ?



  • wie vereinfachst du a*b+b*a ?



  • Ich habe unter Addition diese Regel:
    a(Y*X+Z*X,V) :- a(Y+Z,W), m(W*X,V), !.



  • ths schrieb:

    Ich habe unter Addition diese Regel:
    a(Y*X+Z*X,V) :- a(Y+Z,W), m(W*X,V), !.

    die tut's nicht!
    die vereinfacht nur a*b+a*b.
    ich will aber a*b+b*a.
    und ncht durch aufzähling aller möglichkeiten, denn das wird doof bei
    a*b*c+a*c*b+b*a*c+b*c*a+c*a*b+c*b*a.
    da soll aber 6*a*b*c rauskommen.



  • Hast recht. Werde es mir mal ansehen. Ich bin gerade beim vereinfachen
    von anderen Dingen...



  • Macht's das hier besser?
    a(Y*X+X*Z,V) :- a(Y+Z,W), m(W*X,V)

    Wie sieht's aus, wenn wir noch das da dazunehmen?
    a(Y*X*R+U*X*Z,V) :- a(Y*R+U*Z,W), m(W*X,V)

    Das müßte es doch erlauben X an beliebiger Stelle rauszuziehen. Fehlen zwar noch ein paar Fälle, aber vielleicht nützt's was.

    MfG Jester



  • Naja... du hast wohl den selben Ansatz.

    Ich habe das eben mal probiert: Er macht es 100%ig richtig, solange nur
    die Reihenfolge nicht so wild ist.

    Dann hab ich es für den Fall a*b+b*a probiert: Mit einer Rekursion und einer
    zusätzlichen Zeile gings dann... natürlich auch mit mehr als dreien.

    Meine These:
    Wenn man alle Kombinationen für a*b+b*a in den Code nimmt, geht es.
    Hey -- das sind ein paar Zeilen mehr, ja. Aber dann sollte er es für
    n-Variablen herleiten können 😃 Ich prüfe diese These gerade 😉



  • Nein -- macht er nicht 😞 Hab alle Kombinationen von den 2er-Fall (ab+ab)
    definiert. Aber bei mehr als 2 Variablen schlägt er immer noch fehl, wenn
    die Variablen durcheinander sind...

    @Jester: Nein, deine beiden Vorschläge genügen auch nicht 😞 Ich werd mir
    mal was zu Essen machen, und nochmal drüber nachdenken 👍



  • Jo, genügen tun sie nicht, aber nützen sie was?

    Was ist, wenn wir noch die fehlenden Fälle dazunehmen: eine Variable am rechten/linken Rand, die andere in der Mitte... sind halt 4 Stück, aber das sollte ja nicht so wild sein. Dann müßte er doch in der Lage sein beliebig auszuklammern, oder nicht?

    MfG Jester



  • Ja klar bringen deine beiden was -- nur nicht genug.

    Ich hatte eben alle 2er Kombinationen drinne:

    a(Y*X+Z*X,V) :- a(Y+Z,W), m(W*X,V).
    a(Y*X+X*Z,V) :- a(Y+Z,W), m(W*X,V).
    a(X*Y+Z*X,V) :- a(Y+Z,W), m(W*X,V).
    a(X*Y+X*Z,V) :- a(Y+Z,W), m(W*X,V).
    

    Dann konnte er alles mit 2er Kombinationen ausklammern. Klar.
    Aber bei drei ging es nicht mehr... Er macht es natürlich jetzt
    schon mit n-Variablen -- sie müssen immer nur in der selben
    Reihenfolge stehen 😉



  • Okay, aber wenn Du jetzt die beiden von oben und noch die 4 Fälle, wo einer am Rand steht und einer in der Mitte dazunimmst, dann müßte er doch immer sofort ausklammern können, oder?



  • wir müssen die terme normalisieren, sonst hat das keinen zweck.
    zuerstmal nur produkte.

    * ist eine binäre funktion.
    a*b*c ist kein dreistelliges *(a,b,c), sondern ist
    (a*b)*c

    a*b*(c*(d*e)*fg
    steht innendrin als
    ((a,b),(
    (((c,*(d,e)),f),g)))
    also ein binärbaum.

    der müßte mal geschüttelt und gerüttelt werden, daß alle klammern rausfallen.
    ich arbeite dran aber komme nicht weiter.

    nach dem klammernrausschütteln noch die elemente sortieren und man hat ne gute
    ausgangsbasis für wasauchimmer.



  • volkard schrieb:

    * ist eine binäre funktion.
    a*b*c ist kein dreistelliges *(a,b,c), sondern ist
    (a*b)*c

    Argh! Da bin ich voll reingeflogen...



  • rütteln geht.

    sumToList(X,Result):-
      X=..[+|Parts],
      maplist(sumToList,Parts,Result),
      !.
    sumToList(X,X):-
      atomic(X).
    
    listToSum([H],H):-!.
    listToSum([H|T],Result):-
      listToSum(T,ST),
      Result=H*ST.
    
    normalizeSum(X,Result):-
      sumToList(X,List),
      flatten(List,FList),
      msort(FList,SList),
      listToSum(SList,Result).
    
    :- setof(V,normalizeSum(a+b+a+(c+(d+e)+f+g),V),B),print(user_output,B),nl,flush.
    /*:- setof(V,listToSum([a,b,c,d],V),B),print(user_output,B),nl,flush.*/
    :- halt.
    

    aber der code ist völlig unfruchtbar. ersten fühlt er sich total deplaziert an. irendwie ist das prozedural und ich sae dem kumpel haarklein, was er machen soll. dann brächte ich auch kein prolog zu nehmen. und um so weiterzumchen, muße man ins normalizeSum noch einbauen, daß die einzelnen summanden normalizeProduct aufrufen (geht ja gut mit maplist/3). und zusammenfassen müßte zum normalisieren gehören, daß er im beispiel die beiden hintereinanderstehenden summanden a zu 2*a macht. und schwupps, sind entscheidende vereinfachungsschritte mitten im tiefsten listengewühle.

    das ist absolut nicht das, was ich wünsche.

    die andere richtung, ist alles offen zu lassen, dafür aber viel rechenzeit zu bezahlen. kein simplify mehr, sondern ein transform und transform gibt alle möglichen transformationen aus. eine der möglichkeiten wird schon der weg zu weiteren vereinfachungen sein.

    man hat den auswerter nicht in der hand und kann den suchbaum schlecht kapen, wo man mag. nicht so sachen wie "wenn der baum über 5 transformationen nicht blätter verloren hat, suche ich hier nicht weiter".
    edit: man kann doch. einfach allen transorm-klauseln nen zuätzlichen parameter (zum beispiel noch erlaubte rekursionstiefe) mitgeben. evtl dynamisch anpassen (also mit tiefenboost, wenn man nen schritt gemacht hat, der bestimmt ok war (die schritte im ableiten sind so fälle, die bestimmt ok sind)).


Anmelden zum Antworten