Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Grzegorz 'baziorek' Bazior
Korekta: Paweł Król
Konfiguracja

[Jupyter + xeus-cling] Interaktywne zeszyty przeplatające tekst i interpretowany C++

[artykuł]

Intro

Zeszyt interaktywny to aplikacja przeglądarkowa do tworzenia dokumentów łączących kod na żywo z tekstem narracyjnym, równaniami i wizualizacjami. Jupyter jest to otwarto-źródłowe narzędzie do tworzenia interaktywnych "zeszytów", w których można z poziomu przeglądarki wpisywać kod źródłowy z możliwością jego uruchomienia z funkcjami min. rysowania obrazów, wykresów, widgetów z poziomu przeglądarki, czy wręcz map. Są dostępne frontendy Jupyter console i Qt Console for Jupyter, które teoretycznie powinny działać z każdym kernelem.

Karnele C++ dla Jupyter'a

Narzędzie to aby móc wykonywać kod w danym języku programowania musi mieć zainstalowane odpowiednie środowisko uruchomieniowe, tzw. kernel. Wraz z Jupyterem jest instalowany domyślny kernel IPython obsługujący język Python. Na szczęście Jupyter nie jest ograniczony jedynie do tegoż języka programowania i powstało wiele kerneli do różnych języków programowania, a jeśli jakiegoś języka to osoby ambitne mogą dodać własny kernel. Portal ten jest poświęcony C++, dlatego skupię się na kernelach tego języka, których na moment pisania artykułu, istnieją trzy:
  • CLing - jest to interpreter C++, w repozytorium znajduje się kernel dla Jupytera. Projekt jest rozwijany przez CERN
  • ROOT - narzędzie napisane w C++ do analizy dużej ilości danych, rozwijane przez CERN. W repozytorium znajduje się kernet:
    JupyROOT, który poza wsparciem dla C++ zawiera również dla Pythona i kody obydwu języków programowania można stosować naprzemiennie.
  • xeus-cling - jest to kernel oficjalny do C++, który jednakże używa Clinga, utrzymywanego przez CERN.

Wszystkie wymienione powyżej kernele bazują na CLingu, który jest interpreterem opartym na C++. W wyborze pomaga wpis na oficjalnej stronie standardu C++ z 2017 roku, gdzie został wymieniony artykuł opisujący użycie kernela xeus-cling wraz z innymi przykładami. Przekonujące jest też to, że xeus-cling można sobie przetestować w przeglądarce jeszcze bez jakiejkolwiek instalacji. A już najlepszym argumentem za wyborem tego kernela jest dostępna dokumentacja i kilka artykułów dlatego też wybieram właśnie jego.

Instalacja i pierwsze uruchomienie

Wersje Jupytera

Na oficjalnej stronie projektu Jupyter jest kilka wariantów instalacji (wszystkie open-source i wszystkie można zainstalować):
  • Jupyter Notebook - jest to aplikacja webowa, umożliwiająca tworzenie i udostępnianie dokumentów zawierających kod programów. Wyglądem przypomina dokumenty on-line.
  • JupyterLab - jest to coś jak wyżej, ale agregujące wiele zeszytów, wiele języków programowania z możliwością instalacji pluginów, wiele kart. Poza tym wyglądem przypomina IDE.
  • JupyterHub - zeszyty na wiele użytkowników
Na start wybierzemy zeszyty jedno osobowe, w oparciu o opisane na StackOverflow różnice między Jupyter Notebook a JupyterLab skupię się na nowszym JupyterLab. Decyzja o wyborze wariantu nie jest decyzją permanentną, bo wg informacji z artykułu wiemy, że na jednym komputerze można mieć zainstalowany zarówno JupyterLab jak i Jupyter Notebook, a nawet JupyterHub. Dodam, że przed instalacją można sobie przetestować w przeglądarce wraz z wsparciem dla C++ Jupyter Notebook lub JupyterLab. A tworząc zeszyt dla C++ mamy możliwość wyboru różnych standardów C++.

Instalacja JupyterLab wraz z kernelem xeus-cling

Wg instrukcji można go zainstalować przy pomocy wielu narzędzi, m.in.:
pip
,
conda
,
pipenv
, lub skorzystać z gotowych obrazów dockera.
Z drugiej strony aby zainstalować kernel xeus-cling obsługujący C++, podany jest sposób przy użyciu narzędzia miniconda (ale nie anaconda, która jest odradzana ze względu na gabaryty i konflikt między pakietami), dlatego też z listy sposobów instalacji wybiorę
conda
.

Instalacja miniconda

Po wejściu na stronę dokumentacji microcondy znajdujemy odpowiedni plik dla naszego systemu. Ja testowałem na instalacje na linuxie, dlatego też dokonałem pobrania pliku i instalacji w następujący sposób:
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh
ale dla innych systemów sposób instalacji będzie inny.
Wiele z później opisanych dodatków ma zalecany sposób instalacji przez narzędzie mamba, które jest wg ichniejszej dokumentacji "lepszą alternatywą dla conda", która pierwotnie była nakładką na condę. Dlatego też można od razu zainstalować mambę:
conda install mamba -n base -c conda-forge

a następnie będziemy mogli używać
mamba
 w zastępstwie do
conda
 do instalacji pakietów. Dodam jedynie, że aktywacja środowiska odbywa się w dalszym ciągu przez condę)
Mając już zainstalowanego ww. managera pakietów wystarczy zawołać jedną komendę, ale wpierw zalecają uruchomienie środowiska izolowanego:
conda create -n cling
conda activate cling
Następnie w tym izolowanym środowisku wpisać:
conda install xeus-cling jupyterlab -c conda-forge

Obecnie ww. kernel, wg instrukcji jest wspierany wyłącznie dla systemu Linux. Jak ktoś takiego nie posiada pozostaje mu spróbowanie poniższych kroków w ramach dockera.

Uruchamianie jupytera

Wykonawszy powyższe kroki mamy już od razu jupytera z wsparciem do C++, możemy go uruchomić komendą:
jupyter-lab

jeśli instalowaliśmy go w środowisku izolowanym powinniśmy wpierw wpisać:
conda activate cling
.
po wpisaniu tej komendy uruchamia się nam przeglądarka (http://localhost:8888/lab) i od razu możemy działać zarówno w języku Python, jak i C++ w różnych wersjach, na ten moment: C++11, C++14 i C++17, mamy też dostępny terminal z poziomu przeglądarki i przeglądarkę plików.
Czyli zobaczymy coś takiego:

Czy to bezpieczne?

Jak szybko zauważymy narzędzie ma dostęp do plików, może je edytować, ale dostęp ma jedynie do plików katalogu w którym je uruchomimy, oraz do podkatalogów. Dlatego niepowołany użytkownik nam nie dostanie się do innych plików. Kolejnym zabezpieczeniem jest fakt, że po uruchomieniu Jupytera jest otwierana przeglądarka z odpowiednim tokenem, więc chociażbyśmy weszli na adres jupytera z innego komputera, lub przeglądarki, to bez tokena nie uda się nam zalogować. Mamy też możliwość wygenerowania hasła, którego będziemy mogli używać zamiast tokena.

Jupiter po polsku?

Do Jupytera dostępne są przygotowane paczki językowe, niestety na ten moment języka polskiego nie ma. Jakby był to instalowali byśmy go zapewne komendą:
conda install -c conda-forge jupyterlab-language-pack-pl-PL

może się podejmiesz, drogi Czytelniku?

Pisanie programów C++ w jupyterze - podstawy

Wpierw należało by otworzyć nowy zeszyt (ang. "notebook"), tworząc go mamy możliwość wybrania standardu C++, ja będę przedstawiał przykłady używając C++17. Jak już utworzymy nowy zeszyt z menu, zobaczymy, że ma on rozszerzenie
.ipynb
. Pojawi się nam też pierwsza komórka. Kod programu wprowadza się w tzw. "komórkach", chociaż osoby o naturze hardcora mogą też pisać czystym tekstem, co jest odradzane. Komórki z kodem utrzymują kontekst między sobą w ramach całego zeszytu, dlatego wartość raz prowadzona do zmiennej dostępna jest w innych komórkach. Dla każdej komórki mamy do wyboru kod, lub język Markdown (w ramach komórek typu Markdown możemy tworzyć wzory przy pomocy LaTeX).

Oto przykładowy zeszyt w Jupyterze zawierający  zaimplementowane komórki tekstowe Markdown, kodu, oraz wzór matematyczny wpisany w języku Latex. Rozwinięto listę rodzajów typów komórek.
W przykładzie wykorzystano następujące typu komórek:
1) Poniższy tekst sformatowany wpisano do pierwszej komórki Markdown:
# Nauka Jupytera
Nie jest to takie **trudne**.
## Nauka w C++
Poniżej przykładowy kod:
2) kod źródłowy C++ wykonano wpisując "czystą" treść programiku w C++ do bloku CODE
3) przykładowe równanie matematyczne opisano w języku LaTeX w komórce typu Markdown:
$$c = \sqrt{ a^{2}+b^{2}}$$


Z istotnych rzeczy - komórki dodajemy znakiem plusa, widocznym na powyższym obrazku na górze z prawej. W komórce możemy pisać tak długo dopóki nie przeskoczymy do innej komórki (co może być kliknięciem myszą, bo ENTER domyślnie nie przeskakuje). Wykonać komórkę, czyli zamienić tekst na kod w przypadku języka markdown lub wykonać kod źródłowy robimy przez kombinacje klawiszy: SHIFT + ENTER (alternatywnie przycisk PLAY nad komórkami). Typ komórki między markdown a code możemy zmienić albo z menu nad zeszytem, które na obrazku jest rozwinięte, albo mając zaznaczoną komórkę (zaznaczamy klikając myszką na lewo od komórki) przyciskami: Y - kod, oraz M - markdown. Z bardzo przydatnych rzeczy polecam komendę: CTRL + SHIFT + C, które aktywuje paletę komend, na której pojawiają się komendy wyszukiwane tekstowo, wraz ze skrótami. Jak ktoś ma słabą pamięć lub inne skróty może sobie paletę komend wyklikać z menu: View -> Activate Command Palette. Do rozpoczęcia używania zeszytów wystarczy nam jeszcze poznanie dokumentacji języka Markdown (zaimplementowano go m.in. Github, Gitlab, StackOverflow, Telegram). Jeśli ktoś chce korzystać ze wzorów odsyłam do materiałów poświęconym wyrażeniom matematycznym w LaTeX (język używany powszechniew pracach naukowych z silnym wsparciem zapisu wyrażeń matematycznych). Poza powyższym zachęcam aby sobie jeszcze przeklikać interfejs, aby lepiej zaprzyjaźnić się ze środowiskiem. Jak już ktoś opanuje Jupytera, polecam dostępne skróty z menu kontekstowego Settings -> Advanced settings editor -> Keyboard Shortcuts.

Pisanie w jupyterze w C++

Po pierwsze odbywa się interpretacja języka C++, a nie kompilacja, co prawda jest on zrobiony na bazie Clanga i LLVM, no ale zawsze jest to bardziej jak skrypt. Dlatego też nie piszemy funkcji
int main()
, tylko wszystko piszemy w "globalnym mainie", chociaż możemy tworzyć funkcje/przestrzenie nazw, a nawet używać szablonów, czy zewnętrznych bibliotek. Ważną rzeczą jest fakt, że nie cały kod w zeszycie się uruchamia na żądanie, jedynie aktywna komórka/komórki, której/których uruchomienia zażądamy. Kolejnym istotnym szczegółem jest kontekst wszystkich wykonanych kodów, trzymany między komórkami. Wyczyścić go można przyciskiem na górze "Restart the kernel".

Pisząc w C++ normalnie błędy kompilacji są wykrywane, co widać na poniższym obrazku:
Na wieść o istnieniu interpretera C++ u doświadczonych programistów pojawia się podejrzenie, że ktoś sobie z nas robi żarty. Doświadczenie całkiem nas nie myli, gdyż czasami nie działają pewne fukcjonalności, które powinny działać. Często również komunikat o błędzie nie jest intuicyjny. Przykładowo w momencie pisania tego artykułu natrafiłem na problemy z
std::endl
, które na moje oko nie powinny wystąpić. Na błędy czasami pomaga podział komórki (jak mamy dwie funkcje w jednej komórce to mając błąd
input_line_28:12:1: error: function definition is not allowed here
 możemy go zwalczyć przez podział komórki i wykonanie ich oddzielnie niż jednej. Czasami też pomaga restart kerneli, jednakże wtedy tracimy kontekst z wykonania poprzednich komórek. Dobrą wiadomością jest z kolei fakt, że większość rzeczy działa, w tym szablony, dlatego jest to dobre narzędzie do prowadzenia szkoleń czy nauki C++.

Wypisywanie obiektów

Jest to jedna z zalet interaktywnych zeszytów, dostępna nie tylko w Pythonie. Jeśli obiekt jest typu pierwotnego lub sekwencją (czyli coś co ma metody
begin()
 o
end()
) składającą się z obiektów pierwotnych to można w łatwy sposób wyświetlić zawartość przez (o zgrozo!) pominięcie średnika w ostatniej instrukcji w komórce np.:
C/C++
#include <vector>
#include <iostream>
using namespace std;

std::vector < int > v { 10, 4, 23, 4, 5 };
v
Spowoduje wypisanie:
{ 10, 4, 23, 4, 5 }

Co ciekawe, powyższy mechanizm działa tylko na ostatnią instrukcję w komórce, gdy zrobimy to na instrukcji nieostatniej to pojawi się znany nam wszystkim błąd:
C/C++
#include <vector>
#include <iostream>
using namespace std;

std::vector < int > v { 10, 4, 23, 4, 5 };
v
int i = 44;
i
i dla powyższego kodu ujrzymy komunikat:
input_line_24:4:2: error: expected ';' after expression
v
 ^
 ;

Interpreter Error:
Z kolei jeśli chcieli byśmy skorzystać z "tradycyjnego" mechanizmu wyświetlania funkcją powinniśmy użyć funkcji
xcpp::display()
, która jest dostępna po włączeniu:
#include <xcpp/xdisplay.hpp>
, czego zastosowanie widać w poniższym kodzie:
C/C++
#include <vector>
#include <iostream>
#include <xcpp/xdisplay.hpp>
using namespace std;

std::vector < int > v { 10, 4, 23, 4, 5 };
xcpp::display( v );
int i = 44;
xcpp::display( i );
I wydruk:
{ 10, 4, 23, 4, 5 }
44

Biblioteki zewnętrzne

Jakiż to zysk byłby z pisania w C++ na Jupyterze gdyby się nie dało użyć zewnętrznych bibliotek. Jest to możliwe w
xeus-cling
 dzięki następującym instrukcjom:
C/C++
#pragma cling add_include_path("sciezka/do/naglowkow")
#pragma cling add_library_path("sciezka/do/libow")
#pragma cling load("sciezka/do/bibliotek/dynamicznych")
Przykładowo użyjmy biblioteki ranges-v3, a poniżej przykład sumowania liczb (pobrany + dostosowany z repozytorium renges-v3):
C/C++
#pragma cling add_include_path("/home/agh/Pulpit/range-v3/include/")

#include <iostream>
#include <vector>

#include <range/v3/numeric/accumulate.hpp>
#include <range/v3/view/iota.hpp>
#include <range/v3/view/take.hpp>
#include <range/v3/view/transform.hpp>
using std::cout;

using namespace ranges;
int sum = accumulate( views::ints( 1, unreachable ) | views::transform([ ]( int i ) {
   
return i * i;
} ) | views::take( 10 ),
0 );
// prints: 385
cout << "sum: " << sum << '\n';

W odpowiedzi wypisywane jest:
sum: 385


Dla bibliotek muszą być podane wszystkie ścieżki do includów, jak i do bibliotek (przykład ze stacka, przykład z OpenCV). Zasygnalizowany błąd może być jeszcze mniej intuicyjny niż te, które sygnalizuje kompilator C++ (jak widać da się przebić poziom niejasności kompilatora).

Kolejną moją obserwacją jest, że po dodaniu ścieżki do nowej biblioteki wykonanie kodu może nie zadziałać od razu (includując gtesta zdarzyło mi się, że musiałem 2 sekundy poczekać), dlatego w razie błędu koniecznym może okazać się ponowne wykonanie komórek używających danej biblioteki.

Po co się męczyć - instalacja bibliotek przez conda

Instalujemy
jupyter
 przez
conda
, więc możemy też użyć tegoż mechanizmu do instalacji bibliotek, o ile takowe są dostępne w ramach tegoż managera pakietów. Przykładowo dla biblioteki
boost
, czy
poco
 możemy zawołać:
conda install boost -c conda-forge
conda install poco -c conda-forge
Wtedy nie musimy podawać ścieżek do nagłówków, jednakże do bibliotek skompilowanych już tak. Przykładowo poniżej zmodyfikowany kod z artykułu o obsłudze plików ZIP z poziomu C++:
C/C++
#pragma cling load("libPocoZip.so")

#include <iostream>
#include <fstream>

#include <Poco/Zip/Compress.h>

const char * outputZipFileName = "paczka.zip";
std::ofstream outputZipArchive( outputZipFileName, std::ios::binary );

Poco::Zip::Compress compressor( outputZipArchive, true );

const char * fileToZip = "plikKtoryMusiIstniec.txt";
compressor.addFile( fileToZip, fileToZip );

compressor.close();
outputZipArchive.close();

std::cout << "Archiwum wygenerowano: " << fileToZip << '\n';
Jak wszystko dobrze zrobimy to archiwum ZIP faktycznie powstaje. Co prawda w momencie pisania tego artykułu trzeba było jeszcze na Linuxie zastosować pewien hack aby podmienić wersję GLIBCXX, ale z innymi bibliotekami raczej nie będzie tego problemu (chociaż mogą pojawić się inne wyzwania).

Udogodnienia xeus

Dokumentacja on-line z poziomy Jupytera

Często potrzebujemy sprawdzić coś w dokumentacji C++, w tym celu przykładowo wchodzimy na stronę https://en.cppreference.com/w/ i wyklikujemy lub po prostu wpisuje szukaną frazę w ulubioną wyszukiwarkę, zaawansowani użytkownicy linuxa często używają wtedy manuala. Używając opisywanego kernela dla C++ mamy łatwy sposób do wyświetnenia dokumentacji. Wystarczy wpisać w interaktywnym notatniku tytułu poszukiwanego zagadnienia poprzedzonego zanakiem zapytania. Np. na poniższym obrazku pokazano fragment dokumentacji dla polecenia
?std::string
. Istotnym jest fakt, że dokumentacja ta jest w pełni interaktywna, więc możemy ją przewijać i klikać, a nawet korzystać z wyszukiwarki.

Można podejrzeć możliwe tagi dla standardowej dokumentacji w pliku
${CLING_PREFIX}/share/xeus-cling/tagfiles/cppreference-doxygen-web.tag
, tagi takie można wygenerować przy pomocy doxygena. Aby to zrobić wystarczy ze źródeł html użyć komendy doxytag. Należy też pamiętać o tym, aby wygenerować plik z linkiem do tagów w katalogu:
${CLING_PREFIX}/etc/xeus-cling/tags.d/
, wg wzoru:
{
    "url": "Base URL for the documentation",
    "tagfile": "Name of the doxygen tagfile"
}
co dla standardowej biblioteki wygląta tak:

cat ${CLING_PREFIX}/etc/xeus-cling/tags.d/stl.json
{
    "url": "https://en.cppreference.com/w/",
    "tagfile": "cppreference-doxygen-web.tag"
}

Oprócz doxygena Jupyter wspiera również dokumentacje narzędzia breathe lub sphinx, szczegóły w dokumentacji xeus.

Zawartość multimedialna

Zasadniczo jest to jeden z głównych powodów, dlatego używanie Jupytera jest takie przyjemne, bo do zeszytu można poza kodem, opisem HTML i wzorami wrzucać też multimedia generowane z poziomu języków programowania, w tym również C++.

Zasadniczo wg dokumentacji xeus-cling można wyświetlać dowolny obraz, w tym celu zainspirowany dokumentacją przygotowałem funkcję:
C/C++
#include <string>
#include <fstream>

#include <xtl/xbase64.hpp>
#include <nlohmann/json.hpp>
#include <xcpp/xdisplay.hpp>

namespace im
{
   
struct image
    {
       
inline image( const std::string & filename )
       
{
           
std::ifstream fin( filename, std::ios::binary );
           
m_buffer << fin.rdbuf();
       
}
       
std::stringstream m_buffer;
   
};
   
   
nl::json mime_bundle_repr( const image & i )
   
{
       
auto bundle = nlohmann::json::object();
       
bundle[ "image/png" ] = xtl::base64encode( i.m_buffer.str() );
       
return bundle;
   
}
}
void displayPngImage( const std::string & filename )
{
   
im::image i( filename );
   
xcpp::display( i );
}
Która po uruchomieniu zgodnie z poniższymi poleceniami dane następujący rezultat:

Widgety Xwidgets

Jest to biblioteka widgetów. Powstała specjalnie do użytku z poziomu Jupytera, bazuje na pythonicznym odpowiedniku ipywidgets. Istotnym szczegołem jest fakt, że widgety z Xwidgets są kopiowane przez wartość, co zobaczymy na poniższych przykładach.

Na pierwszy rzut oka widzimy słabą dokumentację xwidgets, na szczęście nie trzeba wyszukiwać w kodzie danego widgetu wyszukując wystąpień
XPROPERTY( ... )
, gdyż xwidgets jest C++ową wersją ipywidgets, która już porządną dokumentację ma.

Instalacja

Instalujemy podobnie, przez manager pakietów
conda
, jednakże poza samym xwidgers musimy jeszcze zainstalować ipywidgets:
conda install ipywidgets -c conda-forge
conda install xwidgets -c conda-forge
Po instalacji uruchamiamy jupytera i działamy.

Suwaki

Abu użyć suwaka należy załączyć nagłówek:
#include <xwidgets/xslider.hpp>
, a obiekt suwaka tworzymy, a następnie wyświetlamy w następujący sposób:
C/C++
xw::slider < double > slider1;
slider1.display();
Dla suwaków możemy ustawić też wartości minimalną, maksymalną, wartość początkową, czy nawet orientacje ("vertical" lub "horizontal"), możemy to zrobić w momencie tworzenia, jak i po utworzeniu. Przykłady suwaków widać na poniższym rysunku:
Jak widać wartości z suwaków można pobrać, oraz suwaki są kopiowane przez wartość.

Przyciski

Można również tworzyć przyciski, w tym celu potrzebujemy użyć obiektu
xw::button
 dostępnego po załączeniu
#include <xwidgets/xbutton.hpp>
. Do przycisku możemy podpiąć funkcję zwrotną/callbacka. Przyciski również można zmieniać i ustawiać, ciekawą możliwością jest możliwość używania predefiniowanych ikon ze strony https://fontawesome.com/v4.7/icons/, możliwości te są zawarte w poniższym obrazku:
Kod dla powyższego (do kopiowania):
C/C++
#include <iostream>
#include <xwidgets/xbutton.hpp>

void f1()
{
   
std::cout << "Kliknieto:" << __FUNCTION__ << std::endl;
}

xw::button button1;
button1.description = "Kliknij mnie";
button1.tooltip = "Jak mnie klikniesz to pojawi sie komunikat";
button1.icon = "chevron-circle-down"; // jedna z https://fontawesome.com/v4.7/icons/
button1.button_style = "info"; // "success", "info", "warning", "danger" lub ""
button1.on_click( f1 );

xw::button button2 = xw::button::initialize().button_style( "success" ).icon( "snowflake-o" ).description( "Niech pada" ).finalize();

button1.display();
button2.display();

Obserwator i validator dla widgetow

Do widgetów z xwidgets można dołączyć funckjonalność biblioteki xproperty o obserwatora i validatowa treści. Przykład tego jest widoczny na poniższym obrazku:
a oto jeszcze kod:
C/C++
#include <iostream>
#include <xwidgets/xnumeral.hpp>

xw::numeral < int > number;
number.min = - 100;
number.description = "Liczba naturalna";
number.display();
XVALIDATE( number, value,[ ]( const auto &, double proposal ) {
   
std::cout << "Validator: propozycja: " << proposal << std::endl;
   
if( proposal < 0 )
   
{
       
throw std::runtime_error( "Tylko liczby dodatnie!" );
   
}
   
return proposal;
} );
XOBSERVE( number, value,[ ]( const auto & s ) {
   
std::cout << "Zmieniono wartosc na: " << s.value << std::endl;
} );

Inne widgety

Jest ich bardzo dużo, wiele z nich znamy z HTMLa, przegląd interaktywny wszystkich obecnie wspieranych widgetów można sobie zobaczyć wchodząc przez Readme Repozytorium Xwidgets na Bindera. Na zakończenie tematu widgetów pozwolę sobie na zademonstrowanie jeszcze widgetów audio-video:
C/C++
#include "xwidgets/ximage.hpp"
#include "xwidgets/xvideo.hpp"
#include "xwidgets/xaudio.hpp"

auto im = xw::image_from_file( "marie.png" ).finalize();
auto vid1 = xw::video_from_file( "Big.Buck.Bunny.mp4" ).finalize();
auto vid2 = xw::video_from_url( "https://webrtc.github.io/samples/src/video/chrome.webm" ).finalize();
auto au = xw::audio_from_file( "Big.Buck.Bunny.mp3" ).finalize();

im.display();
vid1.display();
vid2.display();
au.display();

Poza widgetami zwyczajnymi są też widgety komponujące inne widgety, typu karty. Są też np. funkcje do wyboru koloru.

Dalsze możliwości Jupytera - specjalne komendy

Jest jeszcze wiele ciekawych bibliotek, dedykowanych pod C++sowy kernel Jupytera, ale wpierw wróćmy do możliwości xeus-cling, jakie dają magiczne komendy.
Komendy te zaczynają się od
%
 dla linii, lub
%%
 dla całej komórki. Przykłady ich użycia są krótkie i treściwe w dokumentacji, dlatego pozwolę sobie jedynie na ich opisanie.

  • %%executable
     to komenda, dzięki której zawartość dotychczas uruchomionych komórek może zostać zapisana do pliku wykonywalnego, natomiast komórka, w której znajdzie się ta komenda będzie ciałem funkcji
    main()
    . Istotnym jest możliwość przekazania flag linkera do komendy.
    [run]
    %%file
    umożliwia skopiowanie zawartości komórki do pliku o podanej nazwie
  • %timeit
    komenda wyświetlająca czas wykonywania instrukcji w linii następującej po tejże komendzie.
  • %%timeit
    jw. ale dla całej komórki

Udogodnieniem jest możliwość wywoływania komend powłoki z poziomu Jupytera m.in.
ls
,
ls
,
find
. W tym celu linię należy zacząć od
!
 (wykrzyknika) np.:
!./programWykonywalny


xplot - biblioteka do generowania wykresów

Biblioteka xplot jest nakładką na pythoniczną bibliotekę bqplot-2D, jednakże niestety jest ona będącą we wczesnej fazie rozwoju. Umożliwia ona rysowanie i wyświetlanie wykresów w ramach zeszytu Jupyter.

Jej pełne możliwości można zobaczyć wchodząc przez Readme Repozytorium xplot na Bindera, który zawiera wszystkie przykłady zrealizowane z poziomu C++, natomiast kilka interesujących przykładów (zaciągniętych z repozytorium) znajduje się na poniższych obrazkach:

Aby ją zainstalować wg dokumentacji potrzeba:
conda install xplot -c QuantStack -c conda-forge
conda install widgetsnbextension -c conda-forge
conda install bqplot>=0.11.4,<0.12 -c conda-forge
jupyter labextension install @jupyter-widgets/jupyterlab-manager
jupyter labextension install bqplot@^0.4.3

xtensor (rzekomy odpowiednik NumPy) - numeryczna analiza w oparciu o tablice wielo-wymiarowe

W dokumentacji można sobie przejrzeć możliwości przez wejście na link do Bindera. Z tamtej strony został pobrany poniższy obrazek, demonstrujący przykład działania biblioteki:
Osoby znający NumPy odnajdą się w porównaniu NumPy to xtensor cheatsheet.

Instalacja biblioteki xtensor

Wg dokumentacji wystarczy jedynie:
conda install -c conda-forge xtensor


Biblioteka ta zawiera też podbibliotekę do manipulacji różnymi formatami xtensor-io

xtensor-python - mapowanie pythona z poziomu C++

xtensor-python jest to biblioteka, która umożliwia wykorzystywanie tablic z NumPy z poziomu C++, szczegóły w dokumentacji.

Instalacja xtensor-python

mamba install -c conda-forge xtensor-python


xvega - biblioteka sterowana JSONem do tworzenia wykresów

O tej bibliotece jest poświęcony cały artykuł przeglądowy, dlatego nie będę jej opisywał. Osoby zainspirowane artykułem mogą przeskoczyć do dokumentacji lub do kodu.

xleaflet - biblioteka do interaktywnych map

xleaflet jest to backend do pythonicznej biblioteki ipyleaflet, który umożliwia wizualizacje map z poziomu jupytera.
Możliwe jest m.in.:
  • pobieranie map interaktywnych z różnych stron
  • ustawianie pozycji GPS, typu mapy, zbliżenia
  • możliwość rysowania kształtów na mapie
  • używanie eventów reagujących na obsługę map np. pozycję kursowa myszy
  • zmianę stylów map

Instalacja tej biblioteki wymaga więcej komend, wg dokumentacji należy zastosować poniższe komendy (są to komendy dla jupyter-lab, dla notebooka trzeba by zastosować inne)
mamba install xeus-cling xleaflet -c conda-forge
jupyter labextension install @jupyter-widgets/jupyterlab-manager
jupyter labextension install jupyter-leaflet

Brzmi ciekawie? Zachęcam wpierw aby na github projektu przejrzeć przykłady, można też kliknąć na binderze uruchamiającym xleaflet.

Przykładowo dla poniższego kodu otrzymujemy interaktywną mapę skierowaną na Kraków:
C/C++
#include <xleaflet/xmap.hpp>
#include <xleaflet/xfullscreen_control.hpp>

auto map = xlf::map::initialize()
.
center( { 50.0614300, 19.9365800 } ) // Cracow's GPS
.zoom( 10 )
.
finalize();

map.add_control( xlf::fullscreen_control() );

map.display();

Jako, że na temat używania map z poziomu Jupytera przy użyciu C++ powstał artykuł dlatego też pozwolę sobie pominąć dalszy opis.

xframe - kolejna biblioteka do numerycznych analiz wielowymiarowych tablic

xframe to biblioteka do numerycznych analiz dla wielowymiarowych tablic, zainspirowana przez pandas i xarray.

xwebrtc - biblioteka do obsługi strumieni multimedialnych

xwebrtc jest backendem do WebRTC (narzędzia do obsługi multimediów) i odpowiednikiem pythonicznego ipywebrtc. Biblioteka umożliwia m.in. obsługę kamery, odtwarzanie multimediów, nagrywanie, czy rzekomo prowadzenie rozmów z innymi. Problemem jest fakt, że na githubie biblioteki jest informacja o wczesnej fazie developmentu, dlatego wiele ciekawych funkcjonalności może nie działać.

Instalacja xwebrtc

Na ten moment w dokumentacji jest jedynie informacja o instalacji biblioteki dla przestarzałych zeszytów:
mamba install xeus-cling xwebrtc -c conda-forge

wydaje się, że instalacja dla
jupyter-lab
 nie jest obecnie wspierana (brak informacji w internecie + nie zadziałało u mnie).

Demonstracja

Jak już uda się nam zainstalować bibliotekę możemy przetestować poniższy kod:
C/C++
#include "xwebrtc/xcamera_stream.hpp"
auto camera2 = xwebrtc::camera_facing_user( false ).finalize();
camera2
otrzymując w rezultacie obraz z kamery (czyli w moim przypadku poduszki):

Użycie CUDA z poziomu Jupytera

Wg artykułu o użyciu CUDA w Jupyterze jest to możliwe. Niestety ze względu na brak karty Nvidia'i nie jestem w stanie tego opisać.

Voilà - Gdy nie chcemy instalować serwera jupytera

Jest to narzędzie, które ma z założenia zamieniać zeszyty Jupytera w oddzielne aplikacje webowe. Niestety na moment pisania artykułu to narzędzie nie zadziałało mi z zeszytami z C++. Może jednak za jakiś czas zacznie to działać, wtedy odsyłam do https://github.com/voila-dashboards/voila.

SoS - wiele kerneli w jednym zeszycie

Narzędzie SoS umożliwia używanie wielu kerneli Jupytera w jednym zeszycie, ponadto umożliwia migracje danych między kernelami.
Niestety obecnie oficjalnie wspierane są jedynie wybrane kernele, natomiast przez społeczność został już napisany prosty sos-xeus-cling umożliwiający użycie C++ z poziomu SoSa. Osoby zainteresowane mogą przeczytać opis mechanizmu działania SoS wraz z przykładami.
Mimo dobrych chęci sekcje te należy potraktować na razie jako ciekawostkę, gdyż nie przeszedł on testów integracji między C++ a Pythonem, powiem więcej - kod z C++ w ogóle mi nie zadziałał przy aktywowaniu kernela SoS.

jupyter-console - czyli dla zwolenników konsoli

Jest to alternatywa do webowego jupytera, jej używanie wygląda jak na poniższym obrazku:
Aby ją zainstalować wystarczy:
conda install -c conda-forge jupyter-console

jednakże w moim przypadku niestety zadziałało tylko gdy zainstalowałem przy pomocy:
pip install jupyter-console

Należy pamiętać, że do c++ należy wybrać odpowiedni kernel, jeśli tego nie zrobimy będzie użyty domyślny, czyli do pythona. Wybieramy kernel przy użyciu argumentu:
--kernel=
, szczegóły w powyższym obrazku.
Więcej, aczkolwiek niezbyt wiele informacji w dokumentacji.
Jak widać jupyter-console nie ma możliwości otwierania istniejących zeszytów, jedynie idzie z komendami w locie. Ma natomiast możliwość podpięcia się do uruchomionego kernala (nawet tego z jupyter-lab) i wykonaniu dodatkowych komend na aktualnie działającej sesji.

Współpraca wielu osób nad jednym dokumentem równocześnie

Pierwszą rzeczą jest zainstalowanie dodatku jupyterlab-link-share, dzięki któremy w menu kontekstowym po prawej stronie pojawi się opcja Share -> Share Jupyter server link.
Robimy to komendą:
conda install -c conda-forge jupyterlab-link-share

Dzięki temu będziemy mogli łatwo skopiować linka. Rzekomo też dzięki uruchomieniu jupytera z opcją:
jupyter-lab --collaborative=true
 wiele osób będzie mogło pracować nad tym samym linkiem. Niestety gdy to testowałem nie zadziałało.

Hasło zamiast tokena

Jeśli w pewnym momencie będziemy chcieli mieć dostęp do jupytera pamiętając jedynie hasło, bez zmiennego tokena to możemy włączyć taką opcję, w tym celu piszemy:
jupyter-lab password    
Enter password:
Verify password:
od tej pory uruchomiwszy Jupytera nie jest nam potrzebny token, a jedynie hasło.

Dostęp zdalny

Zabezpieczeniem jest też iż można go uruchomić jedynie z localhosta, aby to zmienić należy uruchomić z flagą:
jupyter-lab --ServerApp.allow_remote_access=true


Połączenie z Jupyter-lab przez HTTPS

Jest to dobrze opisane w oddzielnym artykule. Jako, że zeszyty Jupytera często uruchamiane są lokalnie pozwolę sobie nie opisywać jak włączyć szyfrowanie.

Alternatywa do PowerPoint - pokaz slajdów z Jupytera

Jest to możliwe, oraz wygląda naprawdę dobrze. Co prawda wygodnie wspierane jest na razie tylko przez starszą wersję Jupyter-notebook, gdzie zmieniamy typ komórek na slajdy, następnie zapisujemy i uruchamiamy w specjalny sposób:
jupyter nbconvert plik_ze_slajdami.ipynb --to slides --post serve

Po zrobieniu tego pojawi się nam link do wejścia. Jak to zrobić jest opisane w tym artykule.

Jupyter-hub

Jest to projekt umożliwiający obsługę Jupytera przez wielu użytkowników posiadających swoje konta. Na głównej stronie Jupyter-hub znajdują się dwie wiodące wersje - JupyterHub przy użyciu Kubernetes i Docker, oraz wersja TLJH - The Littlest JupyterHub, która jest łatwa do zainstalowania. Wg ichniejszej dokumentacji jeśli ilość użytkowników będzie do stu to wystarczy wersja prostrza.

Instalacja The Littlest JupyterHub i xeus-cling

Na pierwszej stronie dokumentacji jest informacja o wsparciu dla systemu Ubuntu 18.04 lub nowszych, mi udało się zainstalować to na Ubuntu 21.04. Na ww. stronie dokumentacji jest wiele tutoriali konfiguracyjnych u wielu popularnych dostawrów usług serwerowych (wtym u polskiego OVH), osoby, które chcą mają też poradnik jak zainstalować na własnym serwerze. Zasadniczo instalacja sprowadza się do komend:
sudo apt install python3 python3-dev git curl
curl -L https://tljh.jupyter.org/bootstrap.py | sudo -E python3 - --admin user # nazwa user to nazwa użytkownika, można ustawić inną
Po kilku minutach wszystko będzie gotowe i możemy wejść na adres naszego komputera (może to być
localhost
 lub adres IP w sieci) i logujemy się jako nasz użytkownik - ja zalogowałem się po raz pierwszy nazwą użytkownika jw. a hasło jakie użyjemy za pierwszym razem takie pozostaje. Po zalogowaniu możemy dodać użytkowników (użytkownik na linię), jak również zarządzać użytkownikami, ich serwerami itp.
Pakiety instalujemy nie tak jak wcześniej, tylko wchodzimy przez jupyterHub tworzymy nowy Terminal i tam wpisujemy komendę np.:
sudo -E conda install xeus-cling -c conda-forge

po wylogowaniu i zalogowaniu pojawią się nam zmiany - czyli w tym przypadku możliwość tworzenia zeszytów w C++.
Ważne jest też, aby instalować poprzedzając przez
sudo -E
!
Szczegółowo, a zarazem prosto i obrazowo powyższe kroki są opisane na stronie poświęconej instalacji na naszym serwerze.

Binder

Mając skonfigurowanego Kubernetesa można też skonfigurować na własnym serwerze Bindera, którego znamy z przykładów. Więcej informacji w dokumentacji Bindera. Można też sobie wejść na repozytorium projektu

Repo2Docker - jupyter-notebook niezależny od systemu

Repo2Docker jest narzędziem, które umożliwia utworzenie zeszytu na środowisku izolowanym przez Dockera. Wystarczy podać linka do repozytorium o odpowiedniej strukturze i w rezultacie (po dłuższym czasie) pojawia się nam link do interaktywnego zeszytu.
Aby zainstalować to narzędzie wystarczy:
pip3 install jupyter-repo2docker

Następnie możemy uruchomić narzędzie używając:
jupyter-repo2docker link.do.repozytorium

Podejrzeć sobie przykłady możemy przeglądając repozytoria na stronie https://github.com/binder-examples. Przykład można uruchomić zawołaniem komendy pobierającej wszystko z Githuba:
jupyter-repo2docker https://github.com/binder-examples/zero-to-binder
 lub uruchomić z katalogu lokalnego. Istotnym jest to, że można sobie skonfigurować zależności przez pliki konfiguracyjne, m.in. można skonfigurować:
  • zależności
    apt-get
     przez plik
    apt.txt
  • zależności dla języka Python przez pliki
    requirements.txt
    ,
    setup.py
    ,
    Pipfile
  • zależności conda przez plik
    environment.yml
    , a plik ten można sobie wygenerować automatycznie:
    conda env export -f environment.yml
Więcej informacji w dokumentacji.

Bibliografia