12. Budujemy aplikację

Na wykładzie mówiłem o różnych sposobach tworzenia aplikacji. Tu zajmiemy się narzędziem Application Compiler.

Ponieważ nie będę w stanie sprawdzić przygotowanej przez Państwa Aplikacji — będę prosił o przesłanie jej kodu źródłowego zawierającego w komentarzach opis funkcjonowania.

Zadania…

…Państwa sprowadzą się zatem do:

  1. Wymyślenia jakiejś ciekawej funkcji rozwiązującej zadany problem w MATLABie.
  2. Zaprogramowania jej (i przetestowania)
  3. Konwersji do aplikacji z a pomocą narzędzia Application Compiler
  4. Przetestowania jej.

Ja oczekuję kodu źródłowego (punkt 2 powyżej, polik .m albo .mlx) i krótkiego sprawozdania podsumowującego wyniki. Sprawozdanie może być zawarte na końcu pliku żródłowego jako komentarze albo jako osobny plik…

Przykłady możliwych aplikacji

  1. Program który prosi o wskazanie pliku .csv odpytuje o nazwy (dwu lub więcej) kolumn, które mają być zaprezentowane na wykresie i produkuje wykres.
  2. Program, który będzie rodzajem kalkulatora pozwalającego na wykonanie jakichś obliczeń.

Gdyby zadanie wydawało się za łatwe można spróbować stworzyć aplikację według przepisu podanego na wykładzie korzystając z aplikacji Design App.

Testowanie stworzonej aplikacji

W przypadku aplikacji utworzonej z użyciem Application Compiler powstaje kartoteka zgodna z nazwą aplikacji; przechodzimy (MATLABem) do pod-kartoteki o nazwie for_testing i tam wypisujemy

w przypadku Windows

!app_name parametry

w przypadku linuksa

!./app_name parametry

Więcej informacji na stronie Create Standalone Application from MATLAB Function

Warto wiedzieć

Błędy

Ponieważ zakładamy, że program może (w przypadku, na przykład, kalkulatora) komunikować się z użytkownikiem — w sposób naturalny mogą pojawić się błędy…

Do obsługi takich sytuacji służą polecenia try oraz catch.

Używa się ich w sposób następujący:

try
   statements
catch exception
   statements
end

Wyobraźmy sobie taką sytuację:

A = rand(3);
B = ones(5);
C = [A; B];

Ponieważ rozmiary tablic A i B nie zgadzają się nie da się wykonać operacji wskazanej w trzeciej linii i pojawi się komunikat błędu:

Error using vertcat
Dimensions of arrays being concatenated are not consistent.

Można chronić się przed taką sytuacją w sposób następujący:

try
   C = [A; B];
catch ME
   if (strcmp(ME.identifier,'MATLAB:catenate:dimensionMismatch'))
      msg = ['Dimension mismatch occurred: First argument has ', ...
            num2str(size(A,2)),' columns while second has ', ...
            num2str(size(B,2)),' columns.'];
        causeException = MException('MATLAB:myCode:dimensions',msg);
        ME = addCause(ME,causeException);
   end
   rethrow(ME)
end 

Parę słów na temat ME

  • W przypadku pojawienia się błędu wykonania konstruowany jest obiekt zawierający informacje o błędzie oraz wszystkie niezbędne informacje)
  • catch wychwytuje ten obiekt i sprawdza na czym polega ten błąd, porównując identyfikator błędu (MATLAB:catenate:dimensionMismatch)
  • W przypadku gdy porównanie jest pozytywne konstruuje odpowiedni komunikat informujący o tym (pobiera rozmiary tablic) i następnie
  • używa funkcji MEexception() tworząc „nowy” błąd o identyfikatorze MATLAB:myCode:dimensions i odpowiednio skonstruowanym komunikacie błędu.
  • Funkcja rethrow() „podmienia” błąd na nasz

Trochę to skomplikowane, ale można potrenować, na przykład tak:

try
   C = [A; B];
catch ME
    display(ME.identifier)
    display(ME.message)
end 

teraz wszystko co robimy po wystąpieniu błędu drukujemy najważniejsze składowe tej struktury. Inne składowe to:

  • ME.cause
  • ME.stack (zawiera informację o funkcji/pliku skryptu w której wystąpił błąd (i historii wywołań) oraz numerze linii)

Inne działanie w przypadku wykrycia błędu może wyglądać tak:

try
    a = notaFunction(5,6);
catch
    warning('Problem using function.  Assigning a value of 0.');
    a = 0;
end

Teraz kod chce wywołać funkcję, która w kodzie nie jest dostępna (notaFunction(5,6)), powoduje to błąd, który jest rozwiązywany w ten sposób, że wywołujemy funkcję warning() zawierającą komunikat, a zmiennej a nadajemy wartość zero.

W przypadku gdy może być kilka przyczyn błędu (w powyższym przykładzie istnieje plik notaFunction.m, który nie jest funkcja) można sprawę rozwiązać tak:

try
    a = notaFunction(5,6);
catch ME
    switch ME.identifier
        case 'MATLAB:UndefinedFunction'
            warning('Function is undefined.  Assigning a value of NaN.');
            a = NaN;
        case 'MATLAB:scriptNotAFunction'
            warning(['Attempting to execute script as function. '...
                'Running script and assigning output a value of 0.']);
            notaFunction;
            a = 0;
        otherwise
            rethrow(ME)
    end
end

I wykonanie kończy się albo nadaniem wartości NaN zmiennej a, albo wykonaniem kodu w pliku notaFunction.m i nadani zmiennej a wartości zero.

Funkcja eval()

Funkcja eval() wylicza wartość podanego jako tekst wyrażenia.

>> eval("2*pi")
ans =
    6.2832
>> z=eval("plus(2,3);")
z =
     5
>> eval("y = 2 * 3")
y =
     6

Przekazywanie argumentów do aplikacji

Bardzo prosta funkcja:

function y = apka(a, b, c)
    y = a + b / c;
    display(y);
end

Użycie jej w MATLABie jest proste

>> apka(1, 2, 3);
y =
    1.6667

Gdy uruchamiamy ją po zbudowaniu aplikacji (trzeba wejść do stworzonej kartoteki (apka) i podkartoteki for_testing)

!./apka 1 2 3

da w wyniku

y =
   49.9804

Czemu?

Sprawa jest dosyć skomplikowana, ale otwierając okno terminala (czy to w Windows czy to w linuksie, czy też na Macu) i wydając polecenie, na przykład dir tworzona jest specjalna struktura danych zawierająca tekst polecenia. W przypadku języka C/C++ będzie to tablica typu char** (każdy element tablicy jest wskaźnikiem na wyraz tekstu polecenie); dodatkowo przekazywana jest liczba wyrazów…

(Dygresja:
Jeżeli ktoś programował w C++ to być może pamięta taką minimalną zawartość funkcji main()

#include <iostream>
int main(int argc, char **argv)
{
	return 0;
}

argc zawiera liczbę wyrazów

argv to postać linii wywołującej program

To jest właśnie to.

)

I teraz zmieniłem postać funkcji apka na poniższy:

function y = apka(a, b, c)
    display(a);
    display(b);
    display(c);
    y = a + b / c;
    display(y);
end

Zbudowałem i uruchomiłem

./apka 1 2 3: Signal 121
>> !./apka 1 2 3
a =
    '1'
b =
    '2'
c =
    '3'
y =
   49.9804

Zmienne a, b i c wewnątrz funkcji to wartości znakowe, a operacja wykonywana jest na wartościach kodów ASCII odpowiadających poszczególnym znakom…

Gdy chcemy korzystać z „automatycznego” przekazywania wartości z linii polecenia, trzeba wewnątrz funkcji dokonać konwersji, na przykład za pomocą funkcji str2num() albo str2double() i jest OK

function y = apka(a, b, c)
    if ischar(a)
        a = str2double(a);
    end
    if ischar(b)
        a = str2double(b);
    end
    if ischar(c)
        a = str2double(c);
    end
    y = a + b / c;
    display(y);
end

i teraz już

>> !./apka 1 2 3
y =
    1.6667
Poprzedni
Następny