Wednesday, December 8, 2021

Raspberry OS Bullseye and Cross Toolchain for Linux and Mac OS 10.2.1

Raspberry OS based on Debian bullseye was recently published. The compiler provided with this new version was updated to 10.2.1. As usual, I need to cross build many large projects for the platform, and I had problems in the past with cross toolchains not precisely matching the versions in the target sysroot. I therefore typically prefer to build my own toolchains based on the precise versions of the packages provided with the OS. These are the toolchains I’m using for the past Raspberry Pi systems:

  • Cross toolchain GCC 6.3.0 for Stretch: here.
  • Cross toolchain GCC 8.3.0 for Buster: here.

For Bullseye, these are the new toolchains I’m currently using:

  • Cross toolchain GCC 10.2.1 for Linux hosts for Bullseye: here.
  • Cross toolchain GCC 10.2.1 for Mac OS hosts for Bullseye: here.

As usual, place the toolchain in /opt/rpi, and the sysroot in /opt/rpi/sysroot. I tested these toolchains to build Qt 6.2.1 and everything seems to be fine so far.

Have fun! Bye 😉 

Wednesday, October 20, 2021

Hardware Accelerated Video with Qt 6 on the Raspberry Pi

Getting hardware acceleration into Qt eglfs is tricky. Doing so on a Raspberry Pi is, unfortunately, still tricky after many years. Qt claimed to have reimplemented the Qt Multimedia module entirely, and one of their target was getting hardware acceleration where possibile. So, I thought I could start with a quick look.

Qt 5

Since Raspberry Pi was born, I had to solve the problem of hardware accelerated video in Qt. At the beginning, I wrote POT (PiOmxTextures) to solve this problem: https://github.com/carlonluca/pot. It used OpenMAX to stream decoded video into an OpenGL texture, which was then showed through a custom backend of Qt Multimedia in Qt 5. This approach worked fine, but won’t work on Pi 4/Qt 6. On the other hand, there is another component in the same repo, that includes a custom Qt Quick item to render video through omxplayer. This is the most performant approach, but has its limitations.

Qt Multimedia

I quickly tested Qt Multimedia in Qt 6 on the rpi. My build from this article should support gstreamer. All I got was a a warning on the console. I didn’t investigate further. Maybe I’ll spend more time on this in the future.

POTVL

As the classical POT is no more usable on Raspberry Pi 4, I started to have a look at POTVL, which is very simple to port to Qt 6. With a small patch, it is possible to build it. You’ll find updates on the repo.

Demo

The video is a 1080p video. As you can see, the framerate is acceptable up to a certain weight of the graphics. It seems that Qt 6 OpenGL backend still is a bit less performant than Qt 5 in this specific demo, as you can see from this test from a previous article:


so the result may even improve in the future.
The benchmark app can be found here: https://github.com/carlonluca/Fall.
Unfortunately POTVL is still not future proof, but it is the simplest and most efficient element to port to Qt 6. I may try something better in the near future.
Have fun! Bye 😉

Friday, October 15, 2021

Isogeometric Analysis: Knot Insertion for NURBS

Introduction

In the classical Finite Element Method (FEM) the domain of the problem is approximated by defining a mesh and basis functions over its elements. More elements for the mesh means more basis functions and therefore larger space where approximated solutions can be found. The procedure of increasing the number of elements in the mesh is known as h-refinement. In Isogeometric Analysis (IGA), no mesh is required as the basis functions used to define the domain are the same basis functions used to define the approximate solution. Nevertheless, in IGA, the solution is still a combination of basis functions, so increasing the number of basis functions is still needed to implement some kind of h-refinement. A good way to do this is through knot insertion into the NURBS. This post also presents demo written in Octave and TypeScript.

You can find the full source code for the algorithms and all the examples in this GitHub repo.

Problem of Knot Insertion

Given the general form of a NURBS curve as described here:

$$\boldsymbol{C}^{w}\left(\xi\right)=\sum_{i=0}^{n}N^{p}_{i}\left(\xi\right)\boldsymbol{P}_i^w$$

on the knot vector:

$$\Xi=\left[\xi_0,...,\xi_m\right]$$

knot insertion is the problem of adding the knot $\bar{\xi}\in\left[\xi_k,\xi_{k+1}\right)$ in the knot vector $\Xi$, so that the following still stands:

$$\bar{\boldsymbol{C}}^w\left(\xi\right)=\sum_{i=0}^{n+1}N_{i}^{p}\left(\xi\right)\bar{\boldsymbol{P}}^{w}_{i}=\boldsymbol{C}^w\left(\xi\right),\forall\xi\in\left[\xi_0,\xi_{m+1}\right]$$

on the knot vector:

$$\Xi=\left[\xi_0,...,\xi_{m+1}\right]$$

where $\bar{\boldsymbol{P}}_i^w$ are the control points of the new curve in the projective space.

Knot insertion is supposed to keep the curve unaltered by adding and modifying control points and knots.

Solutions

The problem can then be solved by finding the $n+1$ values $\bar{\boldsymbol{P}}^w_i$, which can be done solving the linear system in $n+2$ equations:

$$\sum_{i=0}^{n+1}N_{i}^{p}\left(\xi\right)\bar{\boldsymbol{P}}^{w}_{i}=\sum_{i=0}^{n}N^{p}_{i}\left(\xi\right)\boldsymbol{P}^w_{i},\forall\xi\in\left\{\xi_0,...,\xi_{n+1}\right\}$$

There is also a simpler way to insert the knots. It can be shown that:

$$\displaylines{\bar{\boldsymbol{P}}_{i}^{w}=\alpha_{i}\boldsymbol{P}_{i}^{w}+\left(1-\alpha_{i}\right)\boldsymbol{P}_{i-1}^{w},\\
\alpha_{i}=\left\{ \begin{array}{ll} 1, & i\leq k-p\\ \dfrac{\bar{\xi}-\xi_{i}}{\xi_{i+p}-\xi_{i}}, & k-p+1\leq i\leq k\\ 0, & i\geq k+1 \end{array}\right.}$$

This is the technique used in all the implementations in the repo.

Octave Implementation

The Octave implementation includes the computeKnotInsertion script that takes a NURBS as input, together with the knot value to add and returns a new knot vector and control points. This is an example of insertion into a circle:

Knot insertion on a circle with related NURBS basis functions.

The computeSurfKnotInsertion script computes knot insertion for NURBS surfaces instead. With the script, knots can be added to both dimensions. An example is shown below:

Knot insertion on a plate with a hole computed in Octave.

TypeScript Implementation

The TypeScript implementation includes a method named insertKnot in the NurbsCurve class. That method allows to insert multiple knots into the vector, returning a new NurbsCurve, identical to the first, but with a different knot vector and different control points.

With a few lines of code:

let circle = new NurbsCirle()
drawNurbsCurve(circle.controlPoints, circle.knotVector, circle.weights, 2, false, true, plot1, null, true, "Knot Insertion 1")
circle.insertKnot(0.6, 6, 0, 1)
drawNurbsCurve(circle.controlPoints, circle.knotVector, circle.weights, 2, false, true, plot2, null, true, "Knot Insertion 2")
circle.insertKnot(0.3, 4, 0, 1)
drawNurbsCurve(circle.controlPoints, circle.knotVector, circle.weights, 2, false, true, plot3, null, true, "Knot Insertion 3")
circle.insertKnot(0.2, 2, 0, 1)
drawNurbsCurve(circle.controlPoints, circle.knotVector, circle.weights, 2, false, true, plot4, null, true, "Knot Insertion 4")

you can draw this result:

Knot insertion on a circle computed in TypeScript.
Live demo

The NurbsSurf class includes the methods insertKnotXi and insertKnotEta to insert knots in the two dimensions of the parametric space and returns the new object NurbsSurf. This is the result of the algorithm applied to a plate:

Knot insertion on a plate with a hole computed in TypeScript.
Live demo

and this is the result on a toroid (in this case knots were not inserted in the entire vector uniformly, but only in a section):

Knot insertion on a toroid computed in TypeScript.
Live demo

As expected, after each insertion, the NURBS is unaltered. This can also be seen by running the unit tests provided in the repo. For example, one unit test includes this code:

function testNurbs(n1: NurbsSurf, n2: NurbsSurf) { for (let xi = 0; xi <= 1; xi += 0.01) for (let eta = 0; eta <= 1; eta += 0.01) assert(approxEqualPoints(n1.evaluate(xi, eta), n2.evaluate(xi, eta))) } [...] measure("surf_knot_insertion_xi", () => { let n1 = new NurbsPlateHole() let n2 = new NurbsPlateHole() for (let i = 0.1; i <= 1; i += 0.9) { if (n2.Xi[i] != i) { let k = BsplineCurve.findSpan(n2.Xi, i, n2.p, n2.controlPoints.length - 1) testNurbs(n1, n2.insertKnotsXi(i, k, 0, 1)) } } })

Bye! ;-)

Saturday, September 25, 2021

Qt 6 on the Raspberry Pi on eglfs

Read this article on WordPress on a Pi! :-)

It has been some time since Qt 6 was released. Now that Qt 6.2 is almost out, I guess it is time to have a look at it.

Building Qt 6

Qt 6 moved from qmake to cmake. This makes a difference when trying to build it and cross-build it. Instructions on how to build are already available online from many sources, so I won’t repeat the theory here. I just want to note a few key points.

Configuration

I configured this build in the simplest form. To start, I want to use eglfs on OpenGL ES.

Toolchain

Building Qt requires a toolchain for building on the host and a toolchain to build for the target (the Raspberry Pi in this case). The toolchain from the host is typically provided by your distro (I used Manjaro in this case), while the toolchain to cross-build for the Raspberry Pi is officially provided here. In the past years I had a lot of weird problems building with those toolchains, so I started to create my own. Never had any problem anymore. Here you can find the toolchains:

  • Raspberry OS Stretch: here;
  • Raspberry OS Buster: here.

Please note that those toolchains are not always able to target every arch. The one for Buster is the one I used in my build, and the target arch is armv8-a.

Dependencies

Dependencies are already listed elsewhere. I would only like to note one thing here: I had problems linking because of a missing symbol: qt_resourceFeatureZstd. According to the sources, it seems that resources are compiled by the rcc tool. My guess is that the host tools use zstd by default to compress when available, but this is a problem when zstd is available on the host and not available on the target. Therefore, I suspect you have to decide if you want zstd support or not before building Qt 6 for the host, and be coherent with the build for the target. I decided to add zstd.

Build Errors

I had a few build errors building Qt 6.2 beta3. Nothing particularly relevant, but I had to apply a few changes. I did not keep track of those changes, they were all trivial.

Build Qt Test App

Once the Qt builds are ready, it is time to run. For the moment, I only want to do a quick test of QML. In my build, I forgot to set eglfs as the default platform, but it can be set it in the environment. I wrote a simple app to test Qt Quick here. The animations are very simple, but I wrote it like this to see how well many items are handled and how well the uniform movement on the y axis is rendered.

NOTE: Unlike Qt5, Qt6 includes host tools and tools built for the target. This means you can build the test app on the target directly or cross-build it from your host. For the package below anyway, you'd need to have a system compatible with those binaries.

First App

This video shows a comparison of this Qt 6 build against Qt 5. It also shows the same benchmark app on Intel UHD and nVidia.

Download

If you want to test the build of Qt 6 on your rpi, you can download it from here:

Download Qt 6.2.0-rc1 for Raspberry OS Buster armv8-a

The package does not include the toolchain, that you can download from the other links, but includes the host build. I don’t think it will be of help, as it is built for my Manjaro context, but I placed it there anyway.

The positions of the directories are relevant:
  • /opt/rpi/rpi-8.6.3: toolchain;
  • /opt/Qt-x64-6.2.0-beta3: host build;
  • /usr/local/Qt-rasp-6.2.0-beta3: position in the target;
  • /opt/rpi/sysroot: sysroot for cross-building.

Runtime Context

The is the 3D environment as seen by Qt 6:

qt.rhi.general: Created OpenGL context QSurfaceFormat(version 2.0, options QFlags<QSurfaceFormat::FormatOption>(), depthBufferSize 24, redBufferSize 8, greenBufferSize 8, blueBufferSize 8, alphaBufferSize 0, stencilBufferSize 8, samples 0, swapBehavior QSurfaceFormat::DefaultSwapBehavior, swapInterval 1, colorSpace QColorSpace(), profile  QSurfaceFormat::NoProfile)
qt.rhi.general: OpenGL VENDOR: Broadcom RENDERER: VC4 V3D 2.1 VERSION: OpenGL ES 2.0 Mesa 19.3.2
qt.scenegraph.general: MSAA sample count for the swapchain is 1. Alpha channel requested = no.
qt.scenegraph.general: rhi texture atlas dimensions: 2048x2048

Bye 😉

Experimental Implementation of the Blog on a Raspberry Pi! 😉

I recently started to add many useful open source services to my Raspberry Pi, like Jenkins, GitLab etc… I wrote some articles about running some services on a arm64 device here:

After these, I also wanted to try WordPress: it is already available as a comfortable docker image for arm64. How could I put it to work? I thought I could reimplement the Bugfree Blog in WordPress!

Unfortunately it was a bit harder than I thought: first of all this bug was making the system completely unstable. I fortunately found a workaround soon by bisecting the kernel, and now there is also an official fix. The fix is not published yet on Ubuntu, but with this docker image I can apply the patches and cross-build the Ubuntu kernel quickly.

A second problem was related to disconnections of USB devices when USB hubs are connected. This is still unresolved.

A third problem is related to the SD card: I never had problems with the SD card, but I recently broke a few. The Raspberry Pi 4 is hotter that I thought, even when load is minimum. The SD cards are guaranteed to work properly up to 85°C, and the Raspberry Pi is set to that max temp, but I tried to reduce it by using a proper case and adding a little (and very professional) Lego castle to encourage heat dissipation. Let’s hope SD cards can live longer.

New URL

The new URL for the experimental Bugfree Blog on Raspberry Pi 64 bit is:


We’ll see if the experiment succeeds, what the performance is and how much I’ll keep it active.
If you are curious, have a look!
Bye 😉

Thursday, September 23, 2021

Docker Image for the Awesome MLDonkey Service

I really like this excellent piece of software. I found some docker images for it for the arm64 architecture, but they seemed not to be very up to date, so I created my own. Here you can find my version of a dockerized MLDonkey service, built for many archs: armv7, arm64, x86 and x64. The arm versions are perfect for Raspberry Pi variants.

The latest version of the image is taken from this commit, which is the latest at the time of writing. It also includes a couple of patches that I sent to add a dark theme.

The image is built on top of debian:buster. For the moment, it is difficult to use something newer because of this known issue.

  • Image on Docker Hub: link;
  • GitHub repo for the project: link.

To run it, you only need a single command:

$ docker run -i -t -v "`pwd`/data:/var/lib/mldonkey" carlonluca/mldonkey

For more options read the readme in the repo.

Bye! 😉

Wednesday, September 8, 2021

Using requirejs in WordPress posts

While porting this blog to WordPress as an experiment, I came across a pretty frequent problem: using requirejs in a WordPress post. In my case, requirejs is needed to use my TypeScript libraries from https://github.com/carlonluca/isogeometric-analysis in the post, to create the plots you see in this post.

It was not simple at all to use it in Google Blogger, WordPress does not seem to behave very differently in this regard. When the post is rendered, I get errors like:

Uncaught Error: Mismatched anonymous define() module: […]

Uncaught Error: Mismatched anonymous define() module: […]
This may be related to the fact that something in WordPress or its plugins looks for that symbol and gets confused when another one is defined in the post. I found many pages treating this problem, but no solution satisfied me, either because it did not work or because I did not like it. I’d prefer the solution to be entirely contained in the post itself, I won’t probably use requirejs often.
This is the way I solved the problem: let’s give the time to WordPress and all its plugins to load and settle. Then, start loading requirejs and all the other deps we need in a script. This is an example for my specific use case:

window.addEventListener("load", function() { $.getScript('https://requirejs.org/docs/release/2.3.6/comments/require.js', function() { $.getScript('https://carlonluca.github.io/isogeometric-analysis/dist/bundle.js', function() { require(["nurbs/drawNurbsCurveExample"], function(d) { d.drawNurbsCurveExample1("nurbsCurve1", true) d.drawNurbsCurveExample2("nurbsCurve2", true, "nurbsBasis2") d.drawNurbsCurveExampleCircle("nurbsCurve3", true, "nurbsBasis3") }) require(["nurbs/drawNurbsSurfExamples"], function(d) { d.drawNurbsSurfPlateHole("nurbsSurf1", true) d.drawNurbsSurfToroid("nurbsSurf2", true) }) }) }) }, false)

To do this, I used jQuery. So, first of all jQuery is loaded. Then, on the onload event, I load requirejs. When requirejs is loaded, I then load my library, which needs requirejs, and then I can freely use it in the post. Note that simply adding script elements was not sufficient, the order must be preserved and the callbacks must be used to ensure the proper chain is respected.
This approach is a little verbose, but allows me to leave the rest of the theme unchanged and confines the mess to the specific posts needing these structures.

Saturday, August 21, 2021

Cross-building the Ubuntu Kernel for the Raspberry Pi 4 (arm64)

I recently had to rebuild the Ubuntu kernel for the Raspberry Pi pretty often to apply some patches. Building on the Pi is simple, but takes A LOT of hours and completely saturates the rpi4. Also, it means installing many dev packages, which I do not typically need. I also tried to do it on a development rpi3: took many hours, failed a few times because of insufficient RAM etc… It is a pain.

I therefore decided to try to cross-build it from my machine, which is not a Ubuntu machine at the moment. Quickest and simplest way I found is to use Docker. The result seems to work, so I decided to also provide the tools I created for myself here: https://github.com/carlonluca/docker-rpi-ubuntu-kernel.

Usage

The image only contains the tools and the libs needed to compile. The kernel itself and the script is provided externally. A workspace directory will contain both the kernel code to build and the output debian packages. With the following command the script is executed in the container and you should get your debs:
docker run --rm -it --name builder -v $PWD/workspace:/workspace \
    -v $PWD/build.sh:/workspace/build.sh carlonluca/docker-rpi-ubuntu-kernel:focal \
    /workspace/build.sh
The workspace directory must contain a src directory with the kernel, so you’ll have to create it yourself and then patch your kernel:
mkdir workspace
cd workspace
git clone https://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux-raspi/+git/hirsute src
[apply needed patches]
The resulting packages can then be moved to the pi and installed with:
sudo dpkg -i *.deb
Hope this tool can be useful! Bye ;-)

Tuesday, August 17, 2021

Docker Image for Qt Development and Continuous Integration on x64 and arm64

Intro

It happens from time to time that I would benefit from having a simple Docker image with all the deps needed to build and run Qt apps on specific Qt versions. For instance, I may need to build some binary on Mac OS for Linux or I may need to have an image for some continuous integration system.

Problem

I had a look around and there are quite a few projects: some do not provide an arm build, some rely on the Qt official installer, that is a bit tricky to automate and tends to change often, some rely on distro-provided packages, that do not provide every version etc… I’d prefer to have a solid build procedure to build images for any Qt version I want. Also, my CI system runs on arm64, so I’d need a multiarch image, and therefore two different Qt builds. I thought it could be simpler to just build Qt on-the-fly in a Dockerfile but… turned out it is not exactly simple.

Solution

At first I created a Dockerfile to:
  1. install all the needed deps on Ubuntu focal;
  2. download Qt sources;
  3. configure;
  4. build;
  5. install;
  6. cleanup.
This proved to be a simple solution, but building Qt for arm on qemu or building on arm took way too much. So I though of a different solution. I created two images:
With the first image Qt can be built and cross-built in a few hours for both x64 and arm64, directly on x64. The builds are then injected into a second multiarch image. This is way faster than building on qemu.

Only one problem left: in Qt 5, crossbuilding Qt results in some Qt tools like qmake to be built for the host arch, which is unacceptable in this case. So, in the second image, a build of the only qtbase module is also needed to build arm64 binaries. Qt 6 works differently instead.

Result

The code to build Qt and all the images are available at: https://github.com/carlonluca/docker-qt.
The image to use for app development is available at: https://hub.docker.com/repository/docker/carlonluca/qt-dev.

Continuous Integration on GitLab with Qt

An example of usage of the dev image is running continuous integration and unit tests on your code on Jenkins or GitLab. For example, I can now run my unit tests in my GitLab instance running on Raspberry Pi 4 with the following code (taken from my project https://github.com/carlonluca/lqobjectserializer):
variables:

GIT_SUBMODULE_STRATEGY: recursive

stages:
  - test_qt5
  - test_qt6

Test_qt5:
  stage: test_qt5
  image:
    name: "carlonluca/qt-dev:5.15.2"
    entrypoint: [""]
  script:
    - cd LQObjectSerializerTest
    - mkdir build
    - cd build
    - cmake ../qt5
    - make
    - ./LQObjectSerializerTest
    - ./LGithubTestCase

Test_qt6:
  stage: test_qt6
  image:
    name: "carlonluca/qt-dev:6.1.2"
    entrypoint: [""]
  script:
    - cd LQObjectSerializerTest
    - mkdir build
    - cd build
    - cmake ../qt6
    - make
    - ./LQObjectSerializerTest
    - ./LGithubTestCase


These images are still experimental though.
Have fun 😉

Bye!

Sunday, July 25, 2021

Dark Theme for MediaWiki in Docker

I recently started to use MediaWiki as a way to store personal information, notes, links etc... It is comfortable to have all that info with me, properly structured, immediately editable online regardless of the operating system and versioned. Adding an instance of MediaWiki to an existent setup is straightforward, and I like to do it with docker. The official docker image of MediaWiki is already multiarch, so I could add it to my Raspberry Pi quickly. Default MediaWiki includes a light theme, but does not seem to include a dark theme. This is where this project by Martynov Maxim comes to help: https://github.com/dolfinus/DarkVector. You'll have to add it to your MediaWiki container and select it for your users.

Keeping MediaWiki up to date (and only usable through HTTPS) is important for security reasons (https://www.mediawiki.org/wiki/Manual:Security) so I created my own MediaWiki multiarch image including that theme by default. You can freely use it: https://hub.docker.com/repository/docker/carlonluca/darkmediawiki. I use it successfully on my aarch64 installation. This is the result:
Refreshing the image is almost effertless thanks to the CI/CD capabilities of GitLab.
For more info refer to the GitHub project: https://github.com/carlonluca/darkmediawiki-docker. Have fun ;-)

Sunday, July 11, 2021

Isogeometric Analysis: NURBS curves and surfaces in Octave and TypeScript

In this blog post I wrote some notes about B-splines. There are however important classes of curves and surfaces that cannot be represented by piecewise-polynomials like circles, ellipses etc... NURBS come to the rescue.

NURBS curves

NURBS is a generalization of B-splines where basis functions are defined with piecewise-rational polynomials. Again the parametric domain is split into multiple ranges by using a knot vector. The general definition is:

$$\boldsymbol{C}\left(\xi\right)=\sum_{i=0}^{n}R_{i}^{p}\left(\xi\right)\boldsymbol{P}_{i},\;a\leq\xi\leq b$$
$\boldsymbol{P}_{i}$ are the control points and the functions $R_{i}^{p}$ are the NURBS basis functions, defined as:

$$R_{i}^{p}\left(\xi\right)=\dfrac{N_{i}^{p}\left(\xi\right)w_{i}}{{\displaystyle\sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)w_{i}}},\;a\leq\xi\leq b,$$
where the functions $N_i^{p}$ are the B-spline basis functions (defined here):

$$N_{i}^{0}\left(\xi\right)=\left\{ \begin{array}{ll}1, & \xi_{i}\leq\xi<\xi_{i+1}\\0,&\text{otherwise}\end{array}\right.,a\leq\xi\leq b$$ $$N_{i}^{p}\left(\xi\right)=\frac{\xi-\xi_{i}}{\xi_{i+p}-\xi_{i}}\cdot N_{i}^{p-1}\left(\xi\right)+\frac{\xi_{i+p+1}-\xi}{\xi_{i+p+1}-\xi_{i+1}}\cdot N_{i+1}^{p-1}\left(\xi\right),a\leq\xi\leq b$$
and the values $w_i$'s are known as weights. The knot vector has the same definition given for B-spline curves:

$$\Xi=\left[\underset{p+1}{\underbrace{a,\ldots,a}},\xi_{p+1},\ldots\xi_{n},\underset{p+1}{\underbrace{b,\ldots,b}}\right],\;\left|\Xi\right|=n+p+2,$$

NURBS surfaces

By using the tensor product we can obtain definitions for NURBS in spaces of higher dimension. For surfaces, given the knot vectors:

$$\Xi=\left[\underset{p+1}{\underbrace{a_{0},\ldots,a_{0}}},\xi_{p+1},\ldots,\xi_{n},\underset{p+1}{\underbrace{b_{0},\ldots,b_{0}}}\right],\left|\Xi\right|=n+p+2$$ $$H=\left[\underset{q+1}{\underbrace{a_{1},\ldots,a_{1}}},\xi_{q+1},\ldots,\xi_{m},\underset{q+1}{\underbrace{b_{1},\ldots,b_{1}}}\right],\left|H\right|=m+q+2$$
a NURBS surface can be defined as:

$$\boldsymbol{S}\left(\xi,\eta\right)=\sum_{i=0}^{n}\sum_{j=0}^{m}R_{i,j}^{p,q}\left(\xi,\eta\right)\boldsymbol{P}_{i,j},\;\left\{ \begin{array}{c}a_{0}\leq\xi\leq b_{0}\\a_{1}\leq\eta\leq b_{1}\end{array}\right.$$
where:

$$R_{i,j}^{p,q}\left(\xi,\eta\right)=\dfrac{N_{i}^{p}\left(\xi\right)N_{j}^{q}\left(\eta\right)w_{i,j}}{{\displaystyle \sum_{\hat{i}=0}^{n}\sum_{\hat{j}=0}^{m}N_{\hat{i}}^{p}\left(\xi\right)N_{\hat{j}}^{q}\left(\eta\right)w_{\hat{i},\hat{j}}}},\;\left\{ \begin{array}{c}a_{0}\leq\xi\leq b_{0}\\a_{1}\leq\eta\leq b_{1}\end{array}\right.$$
and $w_{i,j}$ is the weight.

Homogeneous Coordinates

The implementations found in the repo do not directly implement the summations above, but use instead homogeneous coords to make calculations simpler. Let's consider the general form of a B-spline curve:

$$\boldsymbol{C}\left(\xi\right)=\sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)\boldsymbol{P}_{i},\;a\leq\xi\leq b$$
Control points $\boldsymbol{P}_i$ can be written in homogeneous coords like this:

$$\boldsymbol{P}_{i}^{w}=\left[\begin{array}{c} x_{i}\\ y_{i}\\ z_{i}\\ 1 \end{array}\right]$$
We can multiply each control point by a value $w_i\neq 0$, and the result would still represent the same point in the euclidean space. As a result, we can write:

$$\boldsymbol{C}^{w}\left(\xi\right)=\sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)\cdot\left[\begin{array}{c} x_{i}w_{i}\\ y_{i}w_{i}\\ z_{i}w_{i}\\ w_{i} \end{array}\right]=\left[\begin{array}{c} \sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)x_{i}w_{i}\\ \sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)y_{i}w_{i}\\ \sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)z_{i}w_{i}\\ \sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)w_{i} \end{array}\right]$$
$\boldsymbol{C}^w(\xi)$ is therefore the original B-spline curve in homogeneous coords. Now we can map back it to the euclidean space:

$$\boldsymbol{C}\left(\xi\right)=\left[\begin{array}{c} \frac{\sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)x_{i}w_{i}}{\sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)w_{i}}\\ \frac{\sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)y_{i}w_{i}}{\sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)w_{i}}\\ \frac{\sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)z_{i}w_{i}}{\sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)w_{i}}\\ 1 \end{array}\right]$$
which yields:

$$\boldsymbol{C}\left(\xi\right)=\frac{\sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)w_{i}\boldsymbol{P}_{i}}{\sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)w_{i}}$$
This means that, moving to homogeneous coords, we can use a simpler form. Given:

$$\boldsymbol{P}_{i}^{w}=\left[\begin{array}{c} x_{i}w_{i}\\ y_{i}w_{i}\\ z_{i}w_{i}\\ w_{i} \end{array}\right]$$ we can write a NURBS curve as:

$$\boldsymbol{C}^{w}\left(\xi\right)=\sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)\boldsymbol{P}_{i}^{w}$$
and a NURBS surface as:

$$\boldsymbol{S}^{w}\left(\xi,\eta\right)=\sum_{i=0}^{n}\sum_{j=0}^{m}N_{i}^{p}\left(\xi\right)N_{j}^{q}\left(\eta\right)\boldsymbol{P}_{i}^{w}$$
All the implementations in the repo use these simpler forms.

Octave Implementation

The computeNURBSBasisFun script can be used to compute NURBS basis functions. The drawNURBSBasisFunsP5 draws:

in the plots the effect of the weight is pretty clear. From top to bottom, the degree of the basis funs is increased over the knot vector $ Xi = [0.25, 0.5, 0.75]$.
The computeNURBSCurvePoint script uses homogeneous coords to compute a NURBS curve using B-spline basis functions. We can build curves similar to what we could draw with B-splines but we can also draw circles:

This is an example that shows what happens when a weight is increased on one point:

For NURBS surfaces instead we can draw bivariate basis functions. In these two examples, the first shows what happens when weights are all equal to 1, the second when weights are not all equal:



Now with the computeNURBSSurfPoint, using homogeneous coords, we can draw some interesting surfaces. Scripts are included to draw a plate with a hole:
and one is provided to draw a toroid:

TypeScript Implementation

The same implementation is provided for TypeScript, so you can experiment with the browser. With the NurbsCurve you can compute NURBS basis functions:

Saturday, June 26, 2021

Isogeometric Analysis: B-spline Curves and Surfaces in Octave and TypeScript

In the previous blog post here I implemented Bézier curves. There are other important structures that are used in computer graphics and I'd need those structures to define a domain and a solution in IGA for my implementation here: https://github.com/carlonluca/isogeometric-analysis.

Bézier curves are great, but the polynomials needed to describe some curves may need a high degree to satisfy multiple constraints. To overcome this problem, some structures were designed to use piecewise-polynomials instead. Examples in this case are NURBS and B-splines.

B-spline Curves

B-splines are curves that are described using piecewise-polynomials with minimal support. The parametric domain is split into multiple ranges by using a vector. The general definition of a B-spline curve is:

$$\boldsymbol{C}\left(\xi\right)=\sum_{i=0}^{n}N_{i}^{p}\left(\xi\right)\boldsymbol{P}_{i},\;a\leq\xi\leq b$$
where $\boldsymbol{P}_{i}$'s are the control points of the curve and the functions $N_{i}^{p}\left(\xi\right)$ are basis functions defined as:

$$N_{i}^{0}\left(\xi\right)=\left\{ \begin{array}{ll}1, & \xi_{i}\leq\xi<\xi_{i+1}\\0,&\text{otherwise}\end{array}\right.,a\leq\xi\leq b$$ $$N_{i}^{p}\left(\xi\right)=\frac{\xi-\xi_{i}}{\xi_{i+p}-\xi_{i}}\cdot N_{i}^{p-1}\left(\xi\right)+\frac{\xi_{i+p+1}-\xi}{\xi_{i+p+1}-\xi_{i+1}}\cdot N_{i+1}^{p-1}\left(\xi\right),a\leq\xi\leq b$$
The values $\xi_{i}$ are elements of the aforementioned knot vector, defined as:

$$\Xi=\left[\underset{p+1}{\underbrace{a,\ldots,a}},\xi_{p+1},\ldots,\xi_{n},\underset{p+1}{\underbrace{b,\ldots,b}}\right],\left|\Xi\right|=n+p+2$$
where $p$ is the degree of the basis functions. A knot vector in this form is said to be nonperiodic or clamped or open.

B-spline Surfaces

By using the tensor product we can obtain B-splines in spaces of higher dimension. For surfaces, given the knot vectors:

$$\Xi=\left[\underset{p+1}{\underbrace{a_{0},\ldots,a_{0}}},\xi_{p+1},\ldots,\xi_{n},\underset{p+1}{\underbrace{b_{0},\ldots,b_{0}}}\right],\left|\Xi\right|=n+p+2$$ $$H=\left[\underset{q+1}{\underbrace{a_{1},\ldots,a_{1}}},\xi_{q+1},\ldots,\xi_{m},\underset{q+1}{\underbrace{b_{1},\ldots,b_{1}}}\right],\left|H\right|=m+q+2$$
a B-spline surface can be defined as:

$$\boldsymbol{S}\left(\xi,\eta\right)=\sum_{i=0}^{n}\sum_{j=0}^{m}N_{i}^{p}\left(\xi\right)N_{j}^{q}\left(\eta\right)\boldsymbol{P}_{i,j},\;\left\{ \begin{array}{c} a_{0}\leq\xi\leq b_{0}\\ a_{1}\leq\eta\leq b_{1} \end{array}\right.$$
where $p$ and $q$ are the degrees of the polynomials. In the implementations, sometimes I preferred the matrix form of the equations:

$$\boldsymbol{C}\left(\xi\right)=\left[N_{i-p}\left(\xi\right),\ldots,N_{i}\left(\xi\right)\right]\cdot\left[\begin{array}{c} \boldsymbol{P}_{i-p}\\ \vdots\\ \boldsymbol{P}_{i} \end{array}\right],\;\xi\in\left[\xi_{i},\xi_{i+1}\right)$$
For the surfaces instead, the matrix form is: $$\boldsymbol{S}\left(\xi,\eta\right)=\left[N_{i-p}\left(\xi\right),\ldots,N_{i}\left(\xi\right)\right]\cdot\left[\begin{array}{ccc} \boldsymbol{P}_{j-q,i-p} & \cdots & \boldsymbol{P}_{j-q,i}\\ \vdots & \ddots & \vdots\\ \boldsymbol{P}_{j,i-p} & \cdots & \boldsymbol{P}_{j,i} \end{array}\right]\cdot\left[\begin{array}{c} N_{j-q}\left(\eta\right)\\ \vdots\\ N_{j}\left(\eta\right) \end{array}\right],$$ $$\xi\in\left[\xi_{i},\xi_{i+1}\right),\eta\in\left[\eta_{j},\eta_{j+1}\right)$$
These forms leverage a more performant algorithm for the computation of the basis functions, which returns the value of all nonvanishing functions for a point in the parametric space. The Octave implementation is clearly simpler as Octave has native support for matrices, the TypeScript implementation instead includes a basic Matrix2 class that offers the simplest operators.

Octave Implementation

An implementation for Octave is provided in https://github.com/carlonluca/isogeometric-analysis/tree/master/3.4. There are examples to show how to draw B-spline curves with the implementation.
For example, the drawBsplineBasisFuns script shows the B-spline basis functions over the knot vector $\Xi=[0, 0, 0, 1, 2, 3, 4, 4, 5, 5, 5]$ using the provided implementation of the basis functions:



The drawBsplineCurve script draws, instead, the real curve. For example, to draw the B-spline of degree $p=2$ defined over the knot vector $\Xi=[0, 0, 0, 0.25, 0.5, 0.75, 1, 1, 1]$ with control points:

$$\boldsymbol{P}_{0}=\left(0,0,0\right),\;\boldsymbol{P}_{1}=\left(1,1,1\right),\;\boldsymbol{P}_{2}=\left(2,0.5,0\right)$$ $$\boldsymbol{P}_{3}=\left(3,0.5,0\right),\;\boldsymbol{P}_{4}=\left(0.5,1.5,0\right),\;\boldsymbol{P}_{5}=\left(1.5,0,1\right)$$

a simple call to:

drawBsplineCurve(5, 2, [
    0, 0, 0, 0.25, 0.5, 0.75, 1, 1, 1
], [
    0, 0; 1, 1; 2, 0.5; 3, 0.5; 0.5, 1.5; 1.5, 0
]);


draws:



For surfaces, drawBivariateBsplineBasisFuns can be used to draw bivariate basis functions. For example:

drawBivariateBsplineBasisFuns([
    0, 0, 0, 0.5, 1, 1, 1
], 3, 2, [
    0, 0, 0, 0.5, 1, 1, 1
], 3, 2);


gives this result:

By using the defined basis functions, data defined in https://github.com/carlonluca/isogeometric-analysis/blob/master/3.4/defineBsplineRing.m can draw a shape similar to a ring:



Note that this is not yet a toroid.

TypeScript Implementation

A TypeScript implementation is also present in the repo. With a simple code like this:

Thursday, May 13, 2021

Isogeometric Analysis: Bezier Curves and Surfaces in Octave and TypeScript

While working on my sample IGA implementation here https://github.com/carlonluca/isogeometric-analysis, I found myself in need of defining and implementing the math structures used to define the domain and the solution space. This means that I had to implement various algorithms to define structures like Bezier, B-spline, NURBS and T-spline. So this is a quick intro to Bezier curves and surfaces with a couple of implementations.

Power Basis

A simple math structure to handle curves and surfaces is the power basis representation. Power basis representation uses polynomials, which are fast to compute and simple to handle.
A $n^{th}$-degree power basis curve can be defined in the parametric space as:

$$\begin{eqnarray*} \boldsymbol{C}\left(\xi\right) & = & \left(x\left(\xi\right),y\left(\xi\right),z\left(\xi\right)\right)\\ & = & \sum_{i=0}^{n}\boldsymbol{a}_{i}\xi^{i}\\ & = & \left(\left[\boldsymbol{a}_{i}\right]_{i=0}^{n}\right)^{T}\left[\xi^{i}\right]_{i=0}^{n}, \end{eqnarray*}$$


where the functions $\xi^{i}$ are the basis (or blending) functions. Using the tensor product scheme we can also define a power basis surface:

$$\begin{eqnarray*} \boldsymbol{S}\left(\xi,\eta\right) & = & \left(x\left(\xi,\eta\right),y\left(\xi,\eta\right),z\left(\xi,\eta\right)\right)\\ & = & \sum_{i=0}^{n}\sum_{j=0}^{m}\boldsymbol{a}_{i,j}\xi^{i}\eta^{j}\\ & = & \left(\left[\xi^{i}\right]_{i=0}^{n}\right)^{T}\left[\boldsymbol{a}_{i,j}\right]_{i,j=0}^{i=n,j=m}\left[\eta^{j}\right]_{j=0}^{m} \end{eqnarray*}$$


where:

$$ \left\{ \begin{array}{l} \boldsymbol{a}_{i,j}=\left(x_{i,j},y_{i,j},z_{i,j}\right)\\ b\leq\xi\leq c\\ d\leq\eta\leq e \end{array}\right.$$


Bezier

Bezier curves do not increase the space of curves representable by the power basis form, but introduce the concept of control point. Control points convey a clear geometrical meaning, which is very useful during the design process. So, a $n^{th}$-degree Bezier curve is defined as:

$$\boldsymbol{C}\left(\xi\right)=\sum_{i=0}^{n}B_{i}^{n}\left(\xi\right)\boldsymbol{P}_{i},\;a\leq\xi\leq b$$


where $\boldsymbol{P}_{i}$ represents the $i^{th}$ control point and:

$$B_{i}^{n}\left(\xi\right)=\frac{n!\cdot\xi^{i}\left(1-\xi\right)^{n-i}}{i!\cdot\left(n-i\right)!}$$

is the $i^{th}$ basis function (also known as Bernstein polynomial). With the tensor product scheme, we can also define a Bezier surface with:

$$\boldsymbol{S}\left(\xi,\eta\right)=\sum_{i=0}^{n}\sum_{j=0}^{m}B_{i}^{n}\left(\xi\right)B_{j}^{m}\left(\eta\right)\boldsymbol{P}_{i,j},\;\left\{ \begin{array}{l} a\leq\xi\leq b\\ c\leq\eta\leq d \end{array}\right.$$

Octave Implementation

In the repo https://github.com/carlonluca/isogeometric-analysis, you can find a basic implementation of Bezier curves and surfaces written for Octave (and Matlab) in: https://github.com/carlonluca/isogeometric-analysis/tree/master/3.3. The implementation is tested with a few examples. For example, given these control points:

$$\boldsymbol{P}_{0}=\left(0,0\right),\;\boldsymbol{P}_{1}=\left(1,1\right),\;\boldsymbol{P}_{2}=\left(2,0.5\right)$$ $$\boldsymbol{P}_{3}=\left(3,0.5\right),\;\boldsymbol{P}_{4}=\left(0.5,1.5\right),\;\boldsymbol{P}_{5}=\left(1.5,0\right)$$


we can get to this result by using the computeBezier.m script:



We can also define the curve in the 3D space. For example these control points:

$$\boldsymbol{P}_{0}=\left(0,0,0\right),\;\boldsymbol{P}_{1}=\left(1,1,1\right),\;\boldsymbol{P}_{2}=\left(2,0.5,0\right)$$ $$\boldsymbol{P}_{3}=\left(3,0.5,0\right),\;\boldsymbol{P}_{4}=\left(0.5,1.5,0\right),\;\boldsymbol{P}_{5}=\left(1.5,0,1\right)$$

lead to this result:



It may also be interesting to see what happens to a curve when a new control point is added to the previous one:

Another example is provided for Bezier surfaces. From these control points:

$$\boldsymbol{P}_{0}=\left(-3,0,2\right),\;\boldsymbol{P}_{1}=\left(-2,0,6\right),\;\boldsymbol{P}_{2}=\left(-1,0,7\right),\boldsymbol{P}_{3}=\left(0,0,2\right)$$ $$\boldsymbol{P}_{4}=\left(-3,1,2\right),\;\boldsymbol{P}_{5}=\left(-2,1,4\right),\;\boldsymbol{P}_{6}=\left(-1,1,5\right),\;\boldsymbol{P}_{7}=\left(0,1,2.5\right)$$ $$\boldsymbol{P}_{8}=\left(-3,3,0\right),\;\boldsymbol{P}_{9}=\left(-2,3,2.5\right),\;\boldsymbol{P}_{10}=\left(-1,3,4.5\right),\;\boldsymbol{P}_{11}=\left(0,3,6.5\right)$$



this is what the algorithms produce:

Typescript Implementation

What about a browser implemenation? The above algorithms can clearly be implemented in a browser, so this is an attempt written in TypeScript: https://github.com/carlonluca/isogeometric-analysis/tree/master/ts/src/bezier. The BezierCurve object can be used to draw a plot of the first examples:

Tuesday, April 27, 2021

Isogeometric Analysis and Finite Element Method

It's been some years since I completed my dissertation on FEM and Isogeometric Analysis, but I realised I never had the time to organise my code properly and archive it. So I archived a part of it in a new repo here: https://github.com/carlonluca/isogeometric-analysis. It may be useful for someone studying the topic.

The main topic of the dissertation is not the Finite Element Method (FEM) actually, but the first and the second chapters present it and I wrote a basic implementation that works for 1D problems here: https://github.com/carlonluca/isogeometric-analysis/blob/master/2.3/drawFEM1DExample.m. The first example uses the implementation to compute an approximation using FEM.

Problem

In the code, the first example solves the differential equation:

$$\left\{ \begin{array}{ll} \frac{d^{2}u(x)}{dx^{2}}=10 & \forall x\in\Omega=\left(0,1\right)\\ u(x)=0 & x=0\\ u(x)=1 & x=1 \end{array}\right.$$


Exact solution

It is possible to find an exact solution by integrating both parts twice:

$$\iint_{\Omega}u''(x)dxdx=\iint_{\Omega}10dxdx\Rightarrow u(x)=5x^{2}+c_{1}x+c_{2}$$


The solution of the system:

$$\left\{ \begin{array}{ll} u(x)=5x^{2}+c_{1}x+c_{2} & \forall x\in\Omega=\left(0,1\right)\\ u(x)=0 & x=0\\ u(x)=1 & x=1 \end{array}\right.$$


is the exact solution to the problem:

$$u(x)=x\cdot(5x-4)$$

Weak Formulation

To calculate the weak formulation we need to first define a Dirichlet lift, as the boundary conditions are non-homogeneous:

$$u(x)=\gamma(x)+v(x)\Rightarrow(\gamma(x)+v(x))''=10$$


Multiply by a test function $\varphi\in C_{0}^{\infty}(\Omega)$:

$$(\gamma(x)+v(x))''\cdot\varphi(x)=10\cdot\varphi(x)$$


Now integrate both parts:

$$\int_{\Omega}(\gamma(x)+v(x))''\cdot\varphi(x)dx=\int_{\Omega}10\cdot\varphi(x)dx$$


Using the technique of integration by parts:

$$\int_{a}^{b}u(x)v'(x)dx=\left[u(x)v(x)\right]_{a}^{b}-\int_{a}^{b}u'(x)v(x)dx$$


we can get to:

$$\left[\varphi(x)\left(\gamma(x)+v(x)\right)'\right]_{\Omega}-\int_{\Omega}\varphi'(x)\left(\gamma(x)+v(x)\right)'dx=\int_{\Omega}10\varphi(x)dx$$


$\varphi$ is a distribution and it therefore vanishes on the boundary:

$$-\int_{\Omega}\varphi'(x)\left(\gamma(x)+v(x)\right)'dx=\int_{\Omega}10\varphi(x)dx$$


It is now possible to define the two terms:

$$a(v,\varphi)=\int_{\Omega}\varphi'(x)v'(x)dx$$ $$l(\varphi)=-\int_{\Omega}\left(\varphi'(x)\gamma'(x)+10\varphi(x)\right)dx$$


Galerkin Method

At this point, the Galerkin method can be applied if we accept to look for an approximate solution in a space $V_{n}$ of dimension $dim(V_{n})=n$. Thus, assuming $\left\{ v_{i}\right\} _{i=0}^{n-1}$ is a basis for $V_{n}$, then we can write our approx solution:

$$\tilde{v}(x)=\sum_{i=0}^{n-1}\bar{v}_{i}\cdot v_{i}(x)$$


where $\left\{ \bar{v}_{i}\right\} _{i=0}^{n-1}$ are coefficients of the linear combination.
We can create a linear system with $n$ independent equations that can be written in matrix form as:

$$\boldsymbol{S}_{n}\cdot\boldsymbol{\Upsilon}_{n}=\boldsymbol{F}_{n}$$


where:

$$\boldsymbol{S}_{n}=\left[\int_{0}^{1}v_{i}v_{j}dx\right]_{i,j=0}^{n-1}$$ $$\boldsymbol{\Upsilon}_{n}=\left[\bar{v}_{i}\right]_{i=0}^{n-1}$$ $$\boldsymbol{F}_{n}=\left[-\int_{0}^{1}\left(v_{i}'(x)\gamma'(x)+10\gamma(x)\right)dx\right]_{i=0}^{n-1}$$


The basis functions $\left\{ v_{i}\right\} _{i=0}^{n-1}$ can be chosen according to the needs. In the example, simple roof functions are used: https://github.com/carlonluca/isogeometric-analysis/blob/master/2.3/computePhi.m and https://github.com/carlonluca/isogeometric-analysis/blob/master/2.3/computeDphi.m (note that nomenclature in the code is slightly different). Different approximations can be achieved with a different basis for the space $V_{n}$.
A possible choice for the Dirichlet lift is:

$$\gamma(x)=0\cdot v_{0}(x)+1\cdot v_{n-1}(x)$$


which is the one that is used in the example implementation.

Result

The example script solves the problem for $n=2,...,7$. As expected with FEM, the approximation is exact at the nodes. By increasing the dimension of the space where the solution is to be found, the approximation gets closer to the exact curve.

Lagrange Polynomials

In the previous implementation, roof functions were used. It is possible to use piecewise-polynomials of higher order. One possible implementation is Lagrange polynomials.

$$l_{Lag,i}\left(\xi\right)={\displaystyle \prod_{1\leq j\leq p_{m}+1,j\neq i}\dfrac{\left(\xi-y_{j}\right)}{\left(y_{i}-y_{j}\right)},\;i=1,2,\ldots,p_{m}+1}$$


In the repo there is a sample implementation. The demo draws Lagrange polynomials interpolating an increasing number of points:

Monday, March 1, 2021

Versioning and CI/CD on a Raspberry Pi (or other arm embedded devices)

Blog

It's been many years now since I started to use a Pi as a version control server. I recently even started to use it more heavily for my development, using it for running my unit tests and for CI/CD. I list here the alternatives I tested and the results.

Subversion

The first version control system that I used on the Pi was SVN. It does not probably make much sense to use SVN as a version control service in 2021, but I still have old SVN repos that I do not want to migrate to git. If this is the case for you as well, user krisdavison prepared a docker image that includes a SVN server with a web server to browse the repos: https://hub.docker.com/r/krisdavison/svn-server. Unfortunately, krisdavison is only providing amd64 images, so I created my own fork, including images for armv7, arm64, x86 and x64, which should cover Pi versions from 2 to 4: https://hub.docker.com/r/carlonluca/docker-svn.
If you prefer, Raspbian also offers the SVN server package and WebSVN for browsing.

Gitea

At the beginning, I simply installed git in my Raspbian to be able to push to remotes stored in my pi. This was quick, worked well and I could also browse with GitWeb https://git-scm.com/book/en/v2/Git-on-the-Server-GitWeb. Raspbian includes all you need to setup git and GitWeb on Apache.

After some time though, I started to feel this was a bit restrictive. I could benefit from an issue tracker, a better browser experience with syntax highlighting etc... Therefore, I started to think about installing GitLab on my Pi. On my first Pi 2, this was difficult and, as a matter of fact, impossible, due to hardware limitations (it was taking minutes to load the first login page). 1GB of RAM, with other services running, is not enough for GitLab, so I started to look for other options and I found Gitea: https://gitea.io. Gitea is an excellent git service written in Go, it does not provide all the features GitLab offers, but is incredibly fast and requires much much less RAM. Unfortunately, the Gitea team was not providing a docker arm image for my Pi (https://hub.docker.com/r/gitea/gitea), so I started to build my own fork for x86, amd64, armv6, armv7 and armv8. For Pi 2, the armv7 image was working perfectly fine: https://hub.docker.com/r/carlonluca/gitea.

Gitea is a great self-hosted service if you are running on a Pi. It is a git service with many features, it is fast and it is lightweight.

GitLab

Unfortunately, all good things come to an end :-( and my glorious Pi 2 died in a tragical stormy summer night. RIP. I therefore "had" to switch to a shiny new Pi 4, with 4GB of RAM. This led me to think: "hey, what about GitLab now"?!
It seems GitLab is not currently providing docker images for arm64 (or any other arm variant): https://hub.docker.com/r/gitlab/gitlab-ce. I therefore built my own image for arm64: https://hub.docker.com/r/carlonluca/gitlab. The result is awesome. GitLab is a bit heavy, but it works well. Follow the same instructions of the original image (https://docs.gitlab.com/omnibus/docker) and everything should work. It is a bit slower than Gitea, much heavier, but has more to offer. In particular, package repos and the docker registry can be very handy.
Reference repo for GitLab docker image is https://github.com/carlonluca/docker-gitlab.

Jenkins

Another interesting topic in software development is continuous integration and deployment. As I'm used to Jenkins, this is what I looked into at first. There is a good official docker image for Jenkins: https://hub.docker.com/r/jenkins/jenkins. Unfortunately, atm, the image is only provided for amd64. I therefore, again, forked and created my arm64 image: https://hub.docker.com/r/carlonluca/jenkins. On my Pi 4, Jenkins behaves fine. You can also create other docker containers in your Pi and communicate with those containers from the Jenkins container. Everything seems to be working fine.

GitLab Runner

After some time, I wanted to also try the CI/CD feature included in my GitLab arm64 image. I therefore created a few gitlab configurations and configured a GitLab runner on the same Pi 4. The configuration of the runner was a bit tricky, but seems to be possible. In particular I had troubles using my internal DNS service, but host networking for the runner seems to work so far. Luckily, the runner is already available for arm64: https://hub.docker.com/r/gitlab/gitlab-runner. I created a configuration for a node app and created a CI configuration using a mongo docker image for running my unit tests. Everything worked properly. So you can have your node + angular apps versioned and tested on your arm64 Pi.

Dind

Of course, I also wanted to create a docker image for my node app. So I tried to use the dind service included in GitLab, to see if it could be used on the Pi properly. Yes, it worked :-) So you can have your app versioned on the Pi, tested on the Pi, dockerized on your Pi and uploaded to docker hub or the GitLab registry on the Pi.

Multiarch

The image resulting from the previous step worked great, it can be installed and used properly... on arm64. Also, the images I listed above are pretty long to build and maintain. Gitea, in particular, needs a long building process on an Intel Xeon, because the simplest procedure needs emulators for cross-arch building. Also, I have a slow Internet service, which is even slower when uploading. I was wondering if it could be possible to do all this on the Pi and let it build and transfer for me, instead of keeping my main machine busy... Yes, it is technically possible :-) qemu seems to be able to emulate other arm archs and amd64 on an arm64 kernel.
To do this, I needed an image including docker and buildkit for arm64: I couldn't find one, so I created one for me: https://hub.docker.com/r/carlonluca/docker-multiarch. At this point I wrote the gitlab CI file and tested. Unfortunately, I got less encouraging results this time: I got many types of failures, probably due to several unrelated causes. I could however identify some:
  • slow Internet connection: it seems that docker or buildkit have too short timeouts. Couldn't find a way to set the timeout, but reducing concurrency of builds reduced the frequency of errors (see below).
  • ubuntu binfmt not properly working in some cases: I used this project this fix this issue: https://hub.docker.com/r/tonistiigi/binfmt. I use it in all my GitLab CI files.
  • buildx building with high concurrency: the authors tried to make full use of the build machine by building the images concurrently. This is reasonable, but... they are not currently providing a way to reduce or remove concurrency when needed... which is less reasonable. On the Pi, concurrent complex builds are difficult/impossible. If a single build requires much RAM, 4 builds concurrently over emulators require really too much RAM. The only way I found to build sequentially is to build and push each arch separately, and then use the manifest tool to merge. Here is an example: https://github.com/carlonluca/darkmediawiki-docker/blob/master/.gitlab-ci.yml.
I'm still working on this topic, but I'm mostly able to multiarch-build on my Pi with GitLab CI/CD. All the docker images I listed above are built this way, including the docker-multiarch image, which requires itself to build itself :-) this is a handy way of keeping them up to date. The only partial exception is Gitea: the build procedure of the image also builds Gitea itself, and there seems to be an issue building for x86. The other archs seem to properly build.

This is a list of the images I'm currently using on my Pi 4 in this context:
* https://hub.docker.com/r/carlonluca/docker-svn
* https://hub.docker.com/r/carlonluca/gitea
* https://hub.docker.com/r/carlonluca/gitlab
* https://hub.docker.com/r/carlonluca/jenkins
* https://hub.docker.com/r/gitlab/gitlab-runner
* https://hub.docker.com/r/carlonluca/docker-multiarch


Have fun! ;-)