31 grudnia 2023

[python] Random

Jednolinijkowiec na wygenerowanie 20 losowych bajtów (Ubuntu 18.04).
import random as r; r.seed(1337); " ".join([f'{b:02x}' for b in r.randbytes(20)])
Wynik:
'03 0d 25 9e 7b e3 ef ec a5 17 84 88 cd b1 ba b5 ff 3c a8 5d'

16 lipca 2020

[python] asyncio - Asynchronous I/O

Asynchronieczne funkcje w Pythonie nazywane są corutines (poprzedza jest słowo kluczowe async albo są udekorowane @asyncio.coroutine). Nie można ich wołać jak zwyczajnych funkcji, trzeba skorzystać ze słowa kluczowego await (podobne do yield) i można to robić tylko wewnątrz innych corutines. await przerywa działanie i oddaje sterowanie do "event loop", które zajmuje się zarządzeniem (przekazywaniem sterowania do corutines), i w której są one rejestrowane. Przydatne linki: Biblioteka posiada całe mnóstwo funkcji, dla mnie najważniejsze to:
  • create_task() kolejkuje zadanie
  • run_until_complete() uruchomienie corutine (i wszystkie inne zakolejkowane do tej pory zadania) i czeka aż się zakończy (to konkretna). Jeżeli będą jakiś inne zadania w stanie oczekiwania to run_until_complete() nie będzie na nie czekać.
  • run_forever() uruchamia wszystkie zakolejkowane zadania
Przykład:
import asyncio

async def short_task():
    print('short_task before')
    await asyncio.sleep(2)
    print('short_task after')


async def print_task():
    print('print_task')


async def long_task():
    print('long_task before')
    await asyncio.sleep(5)
    print('long_task after')


async def draw_task():
    print('draw_task')


def main():
    loop = asyncio.get_event_loop()

    loop.create_task(print_task())
    loop.create_task(long_task())

    loop.run_until_complete(short_task())
    loop.run_until_complete(draw_task())

    loop.close()


if __name__ == '__main__':
    main()
Program czeka aż zakończą się dwa zadania: short_task i draw_task, wcześniej uruchamiająć long_task. Ponieważ short_task i draw_task kończą się szybciej dostajemy ostrzeżenie, o wciąż działającym long_taks.
print_task
long_task before
short_task before
short_task after
draw_task
Task was destroyed but it is pending!
task: <Task pending coro=<long_task() done, defined at /home/beru/python_asyncio/run_until.py:13> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7f47d9b217d0>()]>>
Bardziej zaawansowany przykład serwera/czatu. Tutaj mamy do czynienia z trzema rodzajami zdarzeń: uruchomienie serwera (otwarcie portów i nasłuchiwanie), oczekiwanie na tekst na stdio oraz oczekiwanie na nadejście wiadomości od klienta. Samo oczekiwanie na tekst składa się z dwóch zdarzeń: pojawienie się tekstu na stdio i zapisanie go do kolejki, oraz odczytanie, gdy coś w tej kolejce się znajduje.

Jest tu kilka kwiatków asyncio, jak np. sposób przekazywanie parametrów do obiektu Chat (przez lambdę). Ale najbardziej dokuczliwym (i dalej nie mam pewności czy zrobiłem to poprawnie) jest sposób zatrzymania programu. Po otrzymaniu komendy "exit" Chat ustawia future na True, co z kolei anuluje najpierw wszystkie zadania, następnie stopuje pętle, a na końcu pętla ta jest jeszcze zatrzymywana.
Jeżeli program jest zabijany przez Ctrl+C, to ustawienie future w obsłudze wyjątku nie zadziałała. Koniecznym stało się wywołanie explicit cancel_all_task(). Samo anulowanie zadań jest też zdaje się być asynchroniczne, więc stop() nie może być zawołane za wcześnie.
import sys
import asyncio


def main():
    queue = asyncio.Queue()
    loop = asyncio.get_event_loop()

    # Start monitoring the fd file descriptor for read availability and invoke
    # callback with the specified arguments once fd is available for reading
    loop.add_reader(sys.stdin, got_stdin_data, queue)

    fut = loop.create_future()
    fut.add_done_callback(cancel_all_task)

    coro = loop.create_server(lambda: Chat(loop, queue, fut), '127.0.0.1', 7777)
    server = loop.run_until_complete(coro)

    # Run until Ctrl+C is pressed or loop is stopped
    try:
        loop.run_forever()
    except KeyboardInterrupt:
        print('[+] Keyboard exception')
        cancel_all_task()

    # Stop server. Closing listening sockets it's done asynchronously, so
    # wait_closed() need to be used to ensure.
    server.close()
    loop.run_until_complete(server.wait_closed())

    loop.close()


def got_stdin_data(queue):
    loop = asyncio.get_event_loop()
    loop.create_task(queue.put(sys.stdin.readline()))


def cancel_all_task(result=None):
    print('[+] Cancel all tasks')
    loop = asyncio.get_event_loop()
    for task in asyncio.Task.all_tasks():
        task.cancel()
    loop.create_task(stop_loop())


async def stop_loop():
    print('[+] Stop loop')
    loop = asyncio.get_event_loop()
    loop.stop()


class Chat(asyncio.Protocol):
    def __init__(self, loop, queue, fut):
        self.loop = loop
        self.queue = queue
        self.fut = fut

    def connection_made(self, transport):
        peername = transport.get_extra_info('peername')
        print('[+] Connection from:', peername)
        self.transport = transport
        self.loop.create_task(self._wait_for_stdin_data())

    def connection_lost(self, exc):
        print('[+] Connection lost')
        self.fut.set_result(True)

    def data_received(self, data):
        message = data.decode()
        print('[+] Data received: {!r}'.format(message))

        if message.strip() == "exit":
            self.fut.set_result(True)

    def _send_reply(self, reply):
        print('[+] Data send: {!r}'.format(reply))
        self.transport.write(reply.encode())
        self.loop.create_task(self._wait_for_stdin_data())

    async def _wait_for_stdin_data(self):
        reply = await self.queue.get()
        self._send_reply(reply)


if __name__ == '__main__':
    main()
W celu połączenia się z serwerm:
nc 127.0.0.1 7777
Działanie:
[+] Connection from: ('127.0.0.1', 45260)
[+] Data received: 'asdf\n'
[+] Data received: 'exit\n'
[+] Cancel all tasks
[+] Stop loop

7 marca 2020

OpenCV - budowanie ze źródeł

Ostatnio musiałem skomplikować OpenCV w wersji Debug. Zresztą wersja dostępna w repozytoriach Ubuntu (19.10) to obecnie 3.2, trochę stara, w porównaniu najnowszą 4.2. Pomocny link:
A tu moje (skondensowane) kroki, żebym nie zapomniał:
# Katalog roboczy
mkdir ~/opencv_workspace
cd ~/opencv_workspace
git clone https://github.com/opencv/opencv.git
git clone https://github.com/opencv/opencv_contrib.git

# Instalacja virualenv dla Python-a. Przyda się numpy i scipy
virtualenv -p python3 venv
source venv/bin/activate
pip install numpy
pip install scipy

# Konfiguracja za pomocą CMake. 
# Wszystko co potrzebne do budowania znajdzie się w katalogu build, 
# a zainstalowane zostanie do katalogu $VIRTUAL_ENV/local/
cd opencv
mkdir build
cmake -B build/ -D CMAKE_BUILD_TYPE=Debug \
    -D OPENCV_EXTRA_MODULES_PATH=../opencv_contrib/modules/ \
    -D CMAKE_INSTALL_PREFIX=$VIRTUAL_ENV/local/ \
    -D PYTHON_EXECUTABLE=$VIRTUAL_ENV/bin/python \
    -D PYTHON_PACKAGES_PATH=$VIRTUAL_ENV/lib/python3.7/site-packages \
    -D INSTALL_PYTHON_EXAMPLES=ON

# Kompilacja i instalacja (do katalogu $VIRTUAL_ENV/local/)
cd build
make -j4
make install
Przykładowy program
#include <opencv2/opencv.hpp>
#include <iostream>

int main() {
    cv::Mat grayImg = cv::imread("color.png", cv::IMREAD_GRAYSCALE);
    cv::imwrite("gray.png", grayImg);
}
Kompilacja:
cd ~/opencv_workspace
g++ -I./venv/local/include/opencv4 -L./venv/local/lib -Wl,-rpath=./venv/local/lib \
    main.cpp \
    -lopencv_core \
    -lopencv_imgcodecs \
    -lopencv_imgproc
Wynik:



12 lutego 2020

[C++17] Parallel algorithms

Biblioteka standardowa w C++17 została rozbudowana o algorytmy, których praca może zostać zrównoleglona. O sposobie wykonania decyduje ExecutionPolicy przekazane do funkcji jako argument. Programista musi zapewnić, że funkcja przekazana do "algorytmu", będzie bezpieczna - nie będzie zależności między danymi (np. modyfikowanie danych, które mogą być odczytywane przez inny równoległy wątek). Dobre wytłumaczenie różnic na stackoverflow, a tutaj małe zestawienie:
  • std::execution::seq - standardowe wykonanie sekwencyjne, bez zrównoleglenia.
  • std::execution::par - równoległe wykonanie (choć nie ma obietnicy, że tak się stanie).
  • std::execution::par_unseq - równoległe wykonanie (choć nie ma obietnicy, że tak się stanie). Wymaga silniejszych gwarancji na to że przeplatane wywołanie funkcji będzie bezpieczne także w obrębie jednego wątku. Nowe procesory oferują taką możliwość dzięki instrukcjom do wektoryzacji - SIMD (Single-Instruction, Multiple-Data) parallelism.
Przykład z funkcją std::reduce, która w działaniu przypomina std::accumulate. Zsumowanie wartości w wektorze:
#include <iostream>
#include <numeric>
#include <execution>
#include <vector>

using namespace std;

int main() {
    vector<int> vec{1, 2, 3, 4};

    int result = std::reduce(std::execution::par,
                             begin(vec),
                             end(vec));

    cout << result << endl;
}
W przypadku gcc (9.2.1 20191008) wymagane było zainstalowanie dodatkowej paczki libtbb-dev (Threading Building Blocks).
$ sudo apt-get install libtbb-dev
$ g++ -std=c++17 main.cpp -ltbb
$ ./a.out

10

11 lutego 2020

[C++] Atomics

Nie miałem do tej pory wiele do czynienia z atomic-ami (inny wpis) w prawdziwym życiu i traktuje je jako niskopoziomowy mechanizm, ale inni chyba lepiej potrafią wykorzystać ich możliwości. Pozwalają na pisanie kodu "lock-free", chociaż bez głębszego zrozumienia ich natury, niekoniecznie będzie to kod szybszy od tego opartego na muteksach. Ciekawy wykład na ich temat poprowadził Fedor Pikus na CppCon 2017: C++ atomics, from basic to advanced. What do they really do? Warto obejrzeć więcej niż raz.

Operacje na atomic-ach odzwierciedlają operacje sprzętowe i gwarantują, że zostaną wykonane w jednej transakcji (atomowo). CPU oferuje sporą liczbę mechanizmów, które są z nimi związane, z tego też względu standardowa biblioteka jest całkiem rozbudowana. Atomic-iem, może być każdy prymitywny typ (tylko takie obiekty mogą pojawić się w rejestrach CPU).
Przykłady:
// Dla
std::atomic<int> x{0};

// Operacje:
++x;           // atomowy pre-increment
x++;           // atomowy post-increment
x += 1;        // atomowy increment
int y = x * 2; // atomowy odczyt x
x = y + 2;     // atomowy zapis do x

// Uwaga, ta operacja jest niewspierana 
x *= 2;        // ERROR

// Atomowy odczyt x, po którym następuje atomowy zapis do x (dwie operacje)
x = x * 2;
W przykładzie poniżej, atomic posłużył do blokowania wątków, tak aby funkcje even/odd drukowały naprzemiennie tekst w momencie inkrementacji. Uwaga, nie ma gwarancji, że wartość counter wyświetlana na ekranie będzie zgodna z tym co było sprawdzane w if. Są to dwie atomowe operacje odczytu z pamięci, a wartość counter może się zmienić pomiędzy nimi.
#include <iostream>
#include <thread>
#include <atomic>

using namespace std;

std::atomic<int> counter{0};

void odd(size_t n) {
    for (size_t i = 0; i < n; i++) {
        if (counter % 2 == 1) {
            cout << "Odd  increment: " << counter << endl;
            counter++;
        } else {
            cout << "Odd  check: " << counter << endl;   // wartość mogła się zmienić
        }

        std::this_thread::sleep_for(std::chrono::milliseconds{20});
    }
}

void even(size_t n) {
    for (size_t i = 0; i < n; i++) {
        if (counter % 2 == 0) {
            cout << "Even increment: " << counter << endl;
            counter++;
        } else {
            cout << "Even check: " << counter << endl;   // wartość mogła się zmienić
        }
        std::this_thread::sleep_for(std::chrono::milliseconds{40});
    }
}

int main() {
    constexpr size_t steps{6};
    std::thread t1{odd, steps};
    std::thread t2{even, steps};

    t1.join();
    t2.join();
}

Wynik:
Odd  check: 0
Even increment: 0
Odd  increment: 1
Even increment: 2
Odd  increment: 3
Odd  check: 4
Even increment: 4
Odd  increment: 5
Odd  check: 6
Even increment: 6
Even check: 7
Even check: 7

8 lutego 2020

[C++17] std::variant i std::visit

Nowa funkcja std::visit w C++17 umożliwia wywołanie funkcji na rzecz jakiegoś obiektu. Mając wiele takich obiektów (np. "odwiedzając" w pętli obiekty zachowane w jakimś kontenerze) mamy gotowy wzorzec wizytator (odwiedzający). Typowym sposobem implementacji takiego wzorca w C++ było skorzystanie z polimorfizmu. std::visit pozwala jednak na wywołanie funkcji na dowolnym obiekcie. Klasy nie muszą mieć wspólnego interfejsu, ale trzeba będzie skorzystać z znanego z biblioteki boost std::variant. W ten sposób można przechowywać obiekty alternatywnych typów (type-safe union).
#include <iostream>
#include <vector>
#include <variant>

using namespace std;

struct Circle {
    void show() const { cout << "Circle" << endl; }
};

struct Rect {
    void show() const { cout << "Rect" << endl; }
};

int main() {
    using Variant = std::variant<Circle, Rect>;

    vector<Variant> vec;
    vec.push_back(Circle{});
    vec.push_back(Rect{});

    for (const auto& v : vec) {
        std::visit([](const auto& obj){ obj.show(); }, v);
    }
}
Wynik:
Circle
Rect

7 lutego 2020

[C++11/17] enable_if vs. if constexpr

enable_if pojawiło się w C++11 i miało za zadanie uelastycznić mechanizm metaprogramowania w szablonach, pozwalając na tworzenie specjalizacji typu, tylko gdy typ spełniał określone warunki. Wraz z type_traits dało się stworzyć naprawdę trudny w zrozumieniu i utrzymaniu kod. Obecnie sam core guadline zaleca, aby jego używanie ograniczyć do minimum, wymieniając enable_if jako przykład złego kodu. Na szczęście wszystko wskazuje na to, że społeczność chce wreszcie zabić nieszczęsne metaprogramowanie w szablonach. Do tego jeszcze jednak długa droga.

Oryginalnym mechanizmem metaprogramowania w C++ były znane z języka C makra, lecz pewnego dnia, Erwin Unruh, przez przypadek odkrył mechanizm dziś znany jako SFINAE (Substitution Failure Is Not An Error). W skrócie, jeżeli kompilatorowi nie uda się wyspecjalizować szablonu będzie próbował dalej. W jego przykładzie wywołując rekursywną specjalizację szablonów, dokonał obliczeń w czasie kompilacji, choć kompilacja zakończyła się niepowodzeniem. Być może, właśnie przez to, że mechanizm ten pojawił się przypadkiem, a nie został wprowadzony z premedytacją, nie jest on przemyślany składniowo, sprawia że wiele osób odrzuca i dzieli społeczność C++ na dwa obozy.

Przykłady poniżej oparłem na prezentacji Nicolai Josuttis - C++17 - The Best Features. Na początek wersja kodu wykorzystująca enable_if przez zwracany typ (drugi parametr - w naszym przypadku std::string).
#include <iostream>
#include <type_traits>

using namespace std;

template<typename T>
std::enable_if_t<std::is_arithmetic_v<T>,
                 std::string>
as_string1(T x) {
    return std::to_string(x) + " [is_arithmetic_v]";
}

template<typename T>
std::enable_if_t<std::is_same_v<T, std::string>,
                 std::string>
as_string1(T x) {
    return x + " [is_same_v]";
}

template<typename T>
std::enable_if_t<!std::is_same_v<T, std::string> && !std::is_arithmetic_v<T>,
                 std::string>
as_string1(T x) {
    return std::string(x) + " [!is_same_v && !is_arithmetic_v]";
}

int main() {
    cout << as_string1(11) << endl;
    cout << as_string1(std::string("12")) << endl;
    cout << as_string1("13") << endl;
}
Wynik:
11 [is_arithmetic_v]
12 [is_same_v]
13 [!is_same_v && !is_arithmetic_v]
Inna wersja, gdzie enable_if pojawia się jako parametr szablonowy. Domyślne argumenty szablonowe, nie są częścią sygnatury funkcji szablonowej, trzeba więc do enable_if przekazać jeszcze dummy typ (int), nie wiem czemu trzeba przypisać do tego 0. Dziwny hack.
#include <iostream>
#include <type_traits>

using namespace std;

template<typename T, std::enable_if_t<std::is_arithmetic_v<T>,
                                      int> = 0>
std::string as_string2(T x) {
    return std::to_string(x) + " [is_arithmetic_v]";
}

template<typename T, std::enable_if_t<std::is_same_v<T, std::string>,
                                     int> = 0>
std::string as_string2(T x) {
    return x + " [is_same_v]";
}

template<typename T, std::enable_if_t<!std::is_same_v<T, std::string> && !std::is_arithmetic_v<T>,
                                      int> = 0>
std::string as_string2(T x) {
    return std::string(x) + " [!is_same_v && !is_arithmetic_v]";
}

int main() {
    cout << as_string2(21) << endl;
    cout << as_string2(std::string("22")) << endl;
    cout << as_string2("23") << endl;
}
Wynik:
21 [is_arithmetic_v]
22 [is_same_v]
23 [!is_same_v && !is_arithmetic_v]
Są jeszcze inne formy zapisu enable_if np. jako parametr funkcji, ale w tej chwili nie jest to istotne. W C++17 pojawił się ciekawy mechanizm if constexpr, który pozwala radzić sobie z dużą liczbą takich specjalizacji, a kod wygląda znacznie bardziej czytelnie.
#include <iostream>
#include <type_traits>

using namespace std;

template<typename T>
std::string as_string3(T x) {
    if constexpr(std::is_arithmetic_v<T>) {
         return std::to_string(x) + " [is_arithmetic_v]";
    } else if constexpr(std::is_same_v<T, std::string>) {
         return x + " [is_same_v]";
    } else {
         return std::string(x)  + " [!is_same_v && !is_arithmetic_v]";
    }
}

int main() {
    cout << as_string3(31) << endl;
    cout << as_string3(std::string("32")) << endl;
    cout << as_string3("33") << endl;
}
Wynik:
31 [is_arithmetic_v]
32 [is_same_v]
33 [!is_same_v && !is_arithmetic_v]

5 lutego 2020

[C++] problemy z rzutowaniem

Chociaż static_cast i dynamic_cast (i inne) w C++ miały być lepszą wersją "surowego" rzutowania znanego z języka C, nie rozwiązały wszystkich problemów. dynamic_cast można stosować tylko tam, gdzie mamy do czynienia z polimorfizmem, w dodatku angażując to RTTI (Run Time Type Information), które może zaważyć na czasie wykonania programu. static_cast z kolei, posiada niezdefiniowane zachowanie jeżeli próbujemy rzutować obiekt w dół hierarchii.

W przykładzie poniżej kompilator nie zaprotestuje, gdy zrzutujemy obiekt klasy Base na klasę Derived. Problem w tym, że obiekt base rezerwuje mniej pamięci (nie ma pola value_b) niż obiekt derived. W konsekwencji, pisane do pola value_b, będzie skutkowało pisaniem po pamięci.
#include <iostream>

using namespace std;

struct Base {
    void fun() { printf("Base method\n"); }
    int value_a;
};

struct Derived : public Base {
    void fun() { printf("Derived method\n"); }
    int value_b;
};


int main() {
    Base* base = new Base{};
    Derived* derived = static_cast<Derived*>(base); // no-error!

    derived->value_a = 1;
    derived->value_b = 2;       // pisanie po pamięci!

    cout << derived->value_a << endl;
    cout << derived->value_b << endl;
}
Tutaj dopisało nam szczęści, program wykonał się prawidłowo.
$ clang++ -std=c++17  main.cpp
$ ./a.out 
1
2
Sprawa wygląda inaczej, gdy dołączymy address sanitizer.
$ clang++ -std=c++17 -fsanitize=address main.cpp
$ ./a.out
==23018==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000014 at pc 0x0000004c63ba bp 0x7fff3301c9b0 sp 0x7fff3301c9a8
WRITE of size 4 at 0x602000000014 thread T0
...

Sanitizer potrafi wykryć tego typu problem, tylko gdy zaczynamy pisać po nie swojej pamięci. Naukowcy: Byoungyoung Lee, Chengyu Song, Taesoo Kim i Wenke Lee z Georgia Institute of Technology, stworzyli jeszcze inne narzędzie do detekcji tego typu problemów. Oparte na LLVM, dodaje do każdego static_cast tablicę z informacjami o hierarchii dziedziczenia (czyli coś w rodzaju tego co posiada dynamic_cast) i w zgrabny sposób informuje gdzie obiekt został stworzony i gdzie źle rzutowany, gdy tylko takie rzutowanie nastąpi. Za swoją pracę zostali nagrodzeni przez Facebooka fajną nagrodą pieniężną.

2 lutego 2020

[C++17] nowy if

Nowy standard wprowadził kilka nowych form zapisu instrukcji warunkowej "if". Jedna z nich będzie przydatna, gdy będziemy chcieli potwierdzić, że inicjalizacja zakończyła się sukcesem.
Stary zapis:
bool success = init(x);
if (success) {
    cout << "x new value: " << x << endl;
}
Nowy zapis ze średnikiem (od C++17):
if (bool success = init(x); success) {
    cout << "x new value: " << x << endl;
}
Nie widziałem niczego podobnego w innych językach, ale wydaje się całkiem eleganckie. Poniżej przykład z std::map::insert, który zwraca dwie wartości: iterator na element (świeżo wstawiony lub stary o tym samym kluczu) oraz flagę informującą czy wstawienia się powiodło.
#include <iostream>
#include <string>
#include <map>

using namespace std;

void insert_to_map(std::map<int, string>& m, std::pair<int, string> p) {
    if (auto [it, success] = m.insert(p); success) {
        cout << "success, new elem: " << it->first << " -> " << it->second << endl;
    } else {
        cout << "fail, old elem:    " << it->first << " -> " << it->second << endl;
    }
}

int main() {
    std::map<int, string> m = { {1, "aaa"} } ;

    auto b = std::make_pair(2, "bbb");
    insert_to_map(m, std::move(b));

    auto c = std::make_pair(1, "ccc");
    insert_to_map(m, std::move(c));

    for (const auto& v : m) {
        cout << v.first << " " << v.second << endl;
    }
}
Wynik:
success, new elem: 2 -> bbb
fail, old elem:    1 -> aaa
1 aaa
2 bbb

30 stycznia 2020

[C++] std::valarray

Ciekaw struktura danych, z której nigdy do tej pory nie korzystałem. std::valarray pozwala na przeprowadzanie operacji matematycznych dla wszystkimi elementami tablicy jednocześnie. Coś w rodzaju NumPy znanego z Python-a.
W przykładzie poniżej, wszystkie elementy tablicy zostały przemnożone przez 10, a następnie zostały zsumowane.
#include <iostream>
#include <valarray>

using namespace std;

int main() {
    std::valarray<int> va{1, 2, 3};

    va *= 10;

    for(const auto& v : va) {
        cout << v << " ";
    }

    cout << endl << va.sum() << endl;
}
Wynik:
10 20 30 
60
valarray oferuje jeszcze kilka innych klas pomocniczych m.in. std::slice, podobne do tego znanego z Python-a.

Działanie slice znane z Python-a:
for (i = start; i < end; i += step)
    append(i)
Wersja std::slice w C++. Moim zdaniem mechanizm ten jest mniej wygodny i mniej intuicyjny.
for (i = start, j = 0; j < size; i += step, j++)
   append(i)
std::valarray posiada również mechanizm znany z NumPy jako "Indexing with Boolean Arrays". To akurat działa bez zarzutu.
#include <iostream>
#include <valarray>

using namespace std;

int main() {
    std::valarray<int> va = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    auto sub_va = std::valarray<int>{va[std::slice{0, va.size()/2, 2}]};
    for (const auto& v : sub_va) {
        cout << v << " ";
    }
    cout << endl;

    va[va > 5]  = -1;
    for (const auto& v : va) {
        cout << v << " ";
    }
}
Wynik:
0 2 4 6 8 
0 1 2 3 4 5 -1 -1 -1 -1

18 stycznia 2020

Nowości w języku C (C11/C18)

Dwa ciekawe wykłady Dana Saksa na temat nowości w standardzie języka C (np. funkcje inline jako zamienniki makr).



A także o odwrocie od C++ w środowisku embedded (na konferencji CppCon 2016).


2 lipca 2018

xterm - vector graphic display

Lata 70. zaowocowały pojawieniem się komputerów firmy Tektronix oferujących wyświetlanie grafiki wektorowej, co spopularyzowało ich użycie przez inżynierów i projektantów, szczególnie w środowiskach CAD.
Tektronics 4014 US map.jpg

By David Gesswein / GNUplot - http://www.pdp8.net/tek4010/tek4010.shtml, CC BY-SA 4.0, Link

Co ciekawe, xterm pozwala na emulowanie tego typu terminali (tek4014/tek4015/tek4012/tek4013/tek4010), umożliwiając wyświetlanie grafiki wektorowej bezpośrednio w konsoli. Dostępna jest emulacja tylko terminali monochromatycznych, a konsola nie pozwala na zamazywania znaku po wciśnięciu Backspace. Być może jest to kwestia, jakiś dodatkowych opcji konfiguracyjnych.
xterm -t
Jednym z programów, który korzysta z tych możliwości jest gnuplot. Wszystko co trzeba zrobić to ustawienie w skrypcie terminala jako xterm, vvtek lub tek40xx (zauważyłem też opcję tek410x, ale xterm nie radzi sobie z jej obsługą). Poniżej przerobiony przeze mnie przykład autorstwa Hagen Wierstorf: "Plotting the world revisited". Rożnica, którą ja dostrzegłem to to, że opcja xterm rysuje grafikę w osobnym oknie, vttek, w tym samym w którym uruchomiany jest gnuplot, a tek40xx jest zdecydowanie najszybsze w rysowaniu.
#!/usr/bin/gnuplot
#
# Plotting the world with the natural earth data set (3D)
#
# Original AUTHOR: Hagen Wierstorf
# Original source: http://www.gnuplotting.org/code/world3d_revisited.gnu
#
# http://www.gnuplotting.org/plotting-the-world-revisited/

reset

# set terminal vttek
# set terminal xterm
set terminal tek40xx

unset key; unset border
set tics scale 0
set lmargin screen 0
set bmargin screen 0
set rmargin screen 1
set tmargin screen 1
set format ''

set mapping spherical
set angles degrees
set hidden3d
# Set xy-plane to intersect z axis at -1 to avoid an offset between the lowest z
# value and the plane
set xyplane at -1
set view 56,81

set parametric
set isosamples 25
set urange[0:360]
set vrange[-90:90]

r = 0.99
splot r*cos(v)*cos(u),r*cos(v)*sin(u),r*sin(v) with lines, \
      'world_110m.txt' with lines
A prezentuje się to tak:

1 lipca 2018

Jupyter VPython 7

Jakoś tak się złożyło, że kiedy ja zabrałem się za bibliotekę VPython w 2016 roku, projekt był właśnie porzucony, a developerzy skupili się na wersji opartej o Jupytera i GlowScript-a. GlowScript-a umożliwia, kompilację kodu python-a do JavaScipt-u. Powstał nawet bardzo fajny serwis trinket.io, który zamyka cały proces tworzenia w przeglądarce. Jako narzędzie edukacyjne, używanie VPythona stało się jeszcze wygodniejsze. Wraz z wersją Ubuntu 18.4, paczka VPythona została usunięta z repozytorium.
Trochę szukania i okazuje się, że nie jest tak źle. Strona VPython informuje o projekcie IVisual, jednak stackoverflow sugerował wersję VPython. Nie wiem jak jest między nimi różnica, zacząłem od tego drugiego i przy nim pozostałem.
Instalacja poszła z pewnymi problemami. Zanim zostanie zainstalowany VPython, trzeba zainstalować paczkę Jupyter (w tej kolejności), jest to spowodowane jakimś bugiem:
mkdir vpython3
cd vpython3/
virtualenv -p python3 venv
source venv/bin/activate
pip3 install jupyter
pip3 install vpython
W API pojawiły się pewne drobne zmiany, dlatego postanowiłem przepisać standardowy przykład "bouncing ball" na nową wersję. Przy okazji odkryłem na stronie projektu informacje o module povexport do przechwytywania obrazu animacji. Wcześniej przechwytywałem obraz z ekranu (za pomocą PIL) i wycinałem obszar w którym pojawia się okno animacji.
Przykład:
import vpython as vp
import povexport

scene = vp.canvas(title='3D scene', 
                  x=0, y=0, width=400, height=400, 
                  center=vp.vector(0, 0, -0), autoscale=False)
vp.display(scene)

starting_height = 4
floor = vp.box(pos=vp.vector(0, 0, 0), length=4, height=0.5, width=4, color=vp.color.green)
ball = vp.sphere(pos=vp.vector(0, starting_height, 0), radius=1, color=vp.color.red)
ball.velocity = vp.vector(0, -1, 0)
dt = 0.01

frame = 0
while 1:
    vp.rate(100)
    ball.pos = ball.pos + ball.velocity*dt
    if ball.pos.y < ball.radius:
        ball.velocity.y = abs(ball.velocity.y)
    else:
        ball.velocity.y = ball.velocity.y - 9.8*dt
    
    file_name = 'img-%04d.pov' % frame
    inclist = ['colors.inc', 'stones.inc', 'woods.inc', 'metals.inc']
    povexport.export(scene, filename=file_name, include_list=inclist)
    frame += 1
        
    if ball.pos.y > starting_height:
        break
Co się zmieniło (dla mnie):
  • nowy moduł importu (zamiast visual jest vpython)
  • parametry przyjmują explicit obiekt vector i nie tolerują trójelementowej krotki
  • funkcji display zmieniła nazwę na canvas
Słów kilka o povexport. Sprytna biblioteka, która zapisuje informacje o bryłach i powierzchni w tekstowym języku/formacie SDL (Scene Description Language). Dane te następnie można przetworzyć za pomocą programu jak povray na format graficzny np. png i dalej skonstruować animację. Daje to całkiem ciekawe możliwości, bowiem VPython w założeniu oferuje tylko proste możliwości graficzne, povray daje dużo więcej możliwości jeżeli chodzi o renderowanie i ray tracing.
Nie obyło się bez małej ingerencji w kod, gdyż domyślnie obliczania ustawień odległości kamery w povexport były zbyt małe. Może coś robiłem nie tak, ostatecznie zmieniłem jedną linijkę.
# ...
# cpos = 1.5*displayscale*canv.camera.pos # 1.5 is a not understood fudge factor
  cpos = 10*displayscale*canv.camera.pos
# ...
W zasadzie, można pokusić się również o stworzenie skryptu, który edytuje pliki .pov i zmienia linijki z ustawieniami kamery. Zmiana w pliku .pov, którą uzyskałem:
camera {
    right <-image_width/image_height, 0, 0>      // vpython uses right-handed coord. system
    location <0.000000, 0.000000, 173.205081>
    up <0.000000, 1.000000, 0.000000>
    look_at <0.000000, 0.000000, 0.000000>
    angle 60.000000
}
Przerobienie plików z formatu .pov na .png:
for i in *.pov; do povray Height=400 Width=400 Display=false Output_File_Type=N $i 2>/dev/null; done
Nowa wersja skryptu ([1], [2]) do tworzenia animacji video/gif. Wyeliminowałem straty, które pojawiały się podczas składaniu video z plików .png, przez zastosowania kodeka H.264 i manipulacji "Constant Rate Factor" (uwaga, z ustawieniami CRF trzeba ostrożnie)
#!/bin/sh

PALETTE_FILE="tmp_pallete.png"
VIDEO_FILE="output_video.mp4"
INPUT_FILES="img-%4d.png"
OUTPUT_FILE="output.gif"
FILTERS="fps=25"

ffmpeg -r 100 -i $INPUT_FILES -c:v libx264 -crf 0 -preset veryslow $VIDEO_FILE
ffmpeg -v warning -i $VIDEO_FILE -vf "$FILTERS,palettegen" -y $PALETTE_FILE
ffmpeg -v warning -i $VIDEO_FILE -i $PALETTE_FILE -lavfi "$FILTERS [x]; [x][1:v] paletteuse" -y $OUTPUT_FILE
Efekt końcowy:

27 czerwca 2018

Debian w wersji netboot

Debian posiada dwa ciekawe obrazy, które instalują system w minimalnej konfiguracji: netboot i netinst. Zacząłem od netboot-a (obraz .iso o rozmiarze 45MB, instalacja pakietów z sieci):
Po instalacji system liczy sobie około 400 pakietów. Jest oczywiście apt-get i ssh, ale także perl, Python, które są najwyraźniej potrzebne do uruchomienia skryptów systemowych. Dla wygody musiał jednak doinstalować kilka rzeczy:
  • aptitude (jest apt-get, ale to jest wygodniejsze)
  • vim (jest nano i vi, ale ciężko mi się z nich korzysta)
  • net-tools (jest ip, ale wolę ifconfig)
  • gpm (mysz w konsoli, w sumie niepotrzebne, gdy skorzysta się z ssh)
  • gcc
  • make
  • linux-headers
Trzy ostatnie potrzebne dla VirtualBox Guest Additions, ale żeby uruchomić jego instalację konieczna była zmiana w /etc/fstab. Aby dało się uruchamiać z cdrom-a binary (VBoxLinuxAdditions.run) trzeba dodać opcję "exec":
$ sudo vim /etc/fstab
/dev/sr0        /media/cdrom0   udf,iso9660 user,noauto,exec     0       0
Ostatecznie system w takiej konfiguracji liczy sobie około 1.5 GB.
Filesystem      Size  Used Avail Use% Mounted on
udev            2.0G     0  2.0G   0% /dev
tmpfs           395M  5.4M  390M   2% /run
/dev/sda1       3.9G  1.5G  2.3G  39% /
tmpfs           2.0G     0  2.0G   0% /dev/shm
tmpfs           5.0M     0  5.0M   0% /run/lock
tmpfs           2.0G     0  2.0G   0% /sys/fs/cgroup
/dev/sr0         56M   56M     0 100% /media/cdrom0
tmpfs           395M     0  395M   0% /run/user/1000
Pewnie można znaleźć coś mniejszego, ale tak wersja (Debian, którego można rozbudować o nowe pakiety), wydaje cię całkiem ciekawą piaskownicą do dalszego eksperymentowania.

24 czerwca 2018

Multivariable Calculus from MIT OpenCourseWare

Trochę na fali ostatniego zamieszania z blokowaniem kilku popularnych kanałów jak Blender i MIT Courses.
Postanowiłem wspomnieć o jednym z nich: "MIT 18.02 Multivariable Calculus, Fall 2007". Genialny kurs jeżeli, ktoś chce uzupełnić/przypomnieć sobie materiał ze studiów. Filmiki, w wypadku następnej blokady przez youtube, zawsze można pobrać, korzystając z linków na stronie kursu:

15 maja 2017

Myśl szybciej, skupiaj się bardziej i pamiętaj więcej.

Chciałem podzielić się linkiem do bardzo dobrego wykładu na temat tego jak ćwiczyć nasz mózg autorstwa Dr. Michaela Merzenicha dla GoogleTechTalks.

17 kwietnia 2017

[VPython] Kolizja dwóch ciał z obrotem

Trochę więcej kodu, bo sama detekcja kolizji jest bardziej skomplikowana (szczególnie dla większej ilości krawędzi).