You are given a list of persons P, a list of capabilities (or skills) C, and a list of all the capabilities per person. A person can have any number of capabilities. For each capability, there is at least one person who possesses it. Find the smallest set of persons so that they, in total, have all capabilities.

(Optional:) If there are multiple possible smallest sets, find them all.

You are given the following data.

**Persons:**

- P1: Sri
- P2: Yuan
- P3: Kim
- P4: Elias

**Capabilities:**

- C1: Juggle
- C2: Speak Spanish
- C3: Do a handstand
- C4: Solve differential equations

**Capabilities per person:**

- Sri (P1) can juggle (C1)
- Yuan (P2) can do a handstand (C3)
- Kim (P3) can speak Spanish (C2) and solve differential equations (C4)
- Elias (P4) can do none of the above

**Solution:**

To have all four capabilities, the first three persons are needed (Sri, Yuan, and Kim). There is no alternative solution.

The data changes to the following.

**Persons:**

- P1: Pia
- P2: Elise
- P3: Ashwin
- P4: Martha
- P5: Nardole

**Capabilities:**

- C1: Play the piano
- C2: Solve a Rubik’s cube in 60 seconds
- C3: Moonwalk
- C4: Do the Heimlich maneuver
- C5: Understand Morse code

**Capabilities per person:**

- P1: C2, C4
- P2: C1
- P3: C3, C4
- P4: C2, C5
- P5: C1, C2, C4

**Solution:**

This example is not quite as obvious. To get a better overview, we can create a “capability matrix” with all persons:

C1 C2 C3 C4 C5 P1 xx xx P2 xx P3 xx xx P4 xx xx P5 xx xx xx

Because Ashwin (P3) is the only one who can moonwalk (C3) and Martha (P4) is the only one who can understand Morse code (C5), they are always needed in the solution set. Those two together already have skills C2, C3, C4, and C5. As Elise (P2) and Nardole (P5) are the only ones who can play the piano, one of them can be chosen to get to an optimal solution. The two possible smallest sets of persons are therefore (sorry, Pia):

- Elise, Ashwin, Martha (P2, P3, P4)
- Ashwin, Martha, Nardole (P3, P4, P5)

A solution for this issue might not only be helpful when trying to have a fun evening but also came up when trying to find the smallest set of Code Owners (which must approve specific files) necessary in a Code Review.

One possible way to look at the problem might be as follows: Create one vertex per person and one per capability. Create a source vertex and connect it to all person vertices with weight 1. Connect the person vertices to each capability vertex for which they have the capability. At last, find the tree of minimum weight that contains all capability vertices. The graph in the case of Example 2 might look like this:

This way of formulating the issue is a Steiner tree problem which seems not to be solvable in polynomial time for the general case. Maybe this variant can be, though? There also seem to be some approximations.

It should also be possible to express this problem with Integer Linear Programming (ILP). Each person might correspond to one decision variable (yes/no), the count of chosen persons should be minimized, and each capability could correspond to a constraint: The count of all chosen persons who have this capability must be greater than 0. The problem would still be NP-hard but finding the actual solution could be outsourced to a solver.

- The number of persons plus the number of capabilities is usually below 200
- The optimal solution (w.r.t. the number of persons) would be nice but is not strictly necessary

The post Spanish Juggling problem appeared first on Xeve.

]]>You are given a list of persons P, a list of capabilities (or skills) C, and a list of all the capabilities per person. A person can have any number of capabilities. For each capability, there is at least one person who possesses it. Find the smallest set of persons so that they, in total, have all capabilities.

(Optional:) If there are multiple possible smallest sets, find them all.

You are given the following data.

**Persons:**

- P1: Sri
- P2: Yuan
- P3: Kim
- P4: Elias

**Capabilities:**

- C1: Juggle
- C2: Speak Spanish
- C3: Do a handstand
- C4: Solve differential equations

**Capabilities per person:**

- Sri (P1) can juggle (C1)
- Yuan (P2) can do a handstand (C3)
- Kim (P3) can speak Spanish (C2) and solve differential equations (C4)
- Elias (P4) can do none of the above

**Solution:**

To have all four capabilities, the first three persons are needed (Sri, Yuan, and Kim). There is no alternative solution.

The data changes to the following.

**Persons:**

- P1: Pia
- P2: Elise
- P3: Ashwin
- P4: Martha
- P5: Nardole

**Capabilities:**

- C1: Play the piano
- C2: Solve a Rubik’s cube in 60 seconds
- C3: Moonwalk
- C4: Do the Heimlich maneuver
- C5: Understand Morse code

**Capabilities per person:**

- P1: C2, C4
- P2: C1
- P3: C3, C4
- P4: C2, C5
- P5: C1, C2, C4

**Solution:**

This example is not quite as obvious. To get a better overview, we can create a “capability matrix” with all persons:

C1 C2 C3 C4 C5 P1 xx xx P2 xx P3 xx xx P4 xx xx P5 xx xx xx

Because Ashwin (P3) is the only one who can moonwalk (C3) and Martha (P4) is the only one who can understand Morse code (C5), they are always needed in the solution set. Those two together already have skills C2, C3, C4, and C5. As Elise (P2) and Nardole (P5) are the only ones who can play the piano, one of them can be chosen to get to an optimal solution. The two possible smallest sets of persons are therefore (sorry, Pia):

- Elise, Ashwin, Martha (P2, P3, P4)
- Ashwin, Martha, Nardole (P3, P4, P5)

A solution for this issue might not only be helpful when trying to have a fun evening but also came up when trying to find the smallest set of Code Owners (which must approve specific files) necessary in a Code Review.

One possible way to look at the problem might be as follows: Create one vertex per person and one per capability. Create a source vertex and connect it to all person vertices with weight 1. Connect the person vertices to each capability vertex for which they have the capability. At last, find the tree of minimum weight that contains all capability vertices. The graph in the case of Example 2 might look like this:

This way of formulating the issue is a Steiner tree problem which seems not to be solvable in polynomial time for the general case. Maybe this variant can be, though? There also seem to be some approximations.

It should also be possible to express this problem with Integer Linear Programming (ILP). Each person might correspond to one decision variable (yes/no), the count of chosen persons should be minimized, and each capability could correspond to a constraint: The count of all chosen persons who have this capability must be greater than 0. The problem would still be NP-hard but finding the actual solution could be outsourced to a solver.

- The number of persons plus the number of capabilities is usually below 200
- The optimal solution (w.r.t. the number of persons) would be nice but is not strictly necessary

The post Spanish Juggling problem appeared first on Xeve.

]]>php_value upload_max_filesize 16G php_value post_max_size 16G

So the issue is reduced to finding out how these values can be changed for the container running the Nextcloud image in the simplest way.

Most of the sites I found while trying to solve this recommend adding or changing an `ini`

file in the container (e.g. setting up a bind mount). But this can actually be solved in a much simpler and potentially less fragile way! Check out this excerpt from an `nextcloud.ini`

file inside the main Nextcloud container:

$ sudo docker exec -it nextcloud_app bash root@42b3ec8ee31f:/var/www/html# grep -E '(upload_max_filesize|post_max_size)' /usr/local/etc/php/conf.d/nextcloud.ini upload_max_filesize=${PHP_UPLOAD_LIMIT} post_max_size=${PHP_UPLOAD_LIMIT}

So the relevant settings are automatically adjusted when you set the `PHP_UPLOAD_LIMIT`

environment variable. You can easily do this in your `docker-compose.yml`

like this:

services: # ... app: image: nextcloud:apache container_name: nextcloud_app # ... environment: # ... - PHP_UPLOAD_LIMIT=16G # ...

Afterwards, the System page in Nextcloud’s administration menu should show something like this:

By the way: the reverse proxy that redirects client requests to the actual application container also has to support large file uploads. But this is already configured by default when using Nextcloud’s sample docker-compose setup.

The post Changing the maximum upload file size with Nextcloud in a docker-compose setup appeared first on Xeve.

]]>php_value upload_max_filesize 16G php_value post_max_size 16G

So the issue is reduced to finding out how these values can be changed for the container running the Nextcloud image in the simplest way.

Most of the sites I found while trying to solve this recommend adding or changing an `ini`

file in the container (e.g. setting up a bind mount). But this can actually be solved in a much simpler and potentially less fragile way! Check out this excerpt from an `nextcloud.ini`

file inside the main Nextcloud container:

$ sudo docker exec -it nextcloud_app bash root@42b3ec8ee31f:/var/www/html# grep -E '(upload_max_filesize|post_max_size)' /usr/local/etc/php/conf.d/nextcloud.ini upload_max_filesize=${PHP_UPLOAD_LIMIT} post_max_size=${PHP_UPLOAD_LIMIT}

So the relevant settings are automatically adjusted when you set the `PHP_UPLOAD_LIMIT`

environment variable. You can easily do this in your `docker-compose.yml`

like this:

services: # ... app: image: nextcloud:apache container_name: nextcloud_app # ... environment: # ... - PHP_UPLOAD_LIMIT=16G # ...

Afterwards, the System page in Nextcloud’s administration menu should show something like this:

By the way: the reverse proxy that redirects client requests to the actual application container also has to support large file uploads. But this is already configured by default when using Nextcloud’s sample docker-compose setup.

The post Changing the maximum upload file size with Nextcloud in a docker-compose setup appeared first on Xeve.

]]>`qemu-img`

.
- Create a backup of all data and check that it works. Messing with filesystems and disk images is a great way to corrupt your data.
- Check that there are no snapshots of the VM for which you want to manipulate the disk image (in one case I noticed a snapshot was created without me noticing it):

- Decide how large the partitions on the disk and the disk itself should be. Use
`fdisk -l`

,`gdisk -l /dev/vda`

(for GPT disks),`df -h`

or other tools.- In this example there is one LVM partition with a root and swap logical volume inside. I decided 645 GiB for the root logical volume and 650 GiB for the LVM physical volume and partition should be enough.

# fdisk -l [...] Device Boot Start End Sectors Size Id Type /dev/vda1 * 2048 4294967295 4294965248 2T 8e Linux LVM Disk /dev/mapper/nicoloud--vg-root: 2 TiB, 2197995651072 bytes, 4292960256 sectors Units: sectors of 1 * 512 = 512 bytes [...] Disk /dev/mapper/nicoloud--vg-swap_1: 976 MiB, 1023410176 bytes, 1998848 sectors [...] # df -h Filesystem Size Used Avail Use% Mounted on [...] /dev/mapper/nicoloud--vg-root 2.0T 523G 1.4T 28% /

- Now is a good time to boot the VM for which you want to manipulate the disk image into GParted Live or another similar image. This is particularly necessary when you want to shrink an ext4 filesystem. Growing it, or e.g. shrinking the physical volume when the filesystem itself is already small enough, is possible online, but it does not hurt to boot into GParted Live anyway.
- Using e.g. a Ubuntu Live image is also possible but probably needs more RAM (I only had 1 GiB available for this VM at the time).

- Resize the filesystem and logical volume in one go (ext4 is definitely supported by the LVM tools – for other filesystems you might have to check):
`lvresize --resizefs --size 645G nicoloud-vg/root`

- Of course you can also run
`e2fsck`

and`resize2fs`

(or the analogous commands for another filesystem) yourself

- Of course you can also run

- Resize the physical volume:
`pvresize --setphysicalvolumesize 650G /dev/vda1`

- Of course you need to make sure all logical volumes fit inside this.

- If
`pvresize`

gives you an error in the direction of`cannot resize to XYZ extents as later ones are allocated`

although your physical volume size would have enough space for all logical volumes, then it is possible that the current physical extents make it impossible to shrink the physical volume. This can happen if e.g. the free space is at the start or middle of the physical volume instead of concentrated at the end.- You can diagnose an issue like this with
`pvs -v --segments /dev/vda1`

and fix it with`pvmove`

. Here is an example:

- You can diagnose an issue like this with

- After fixing such issues,
`pvresize`

should work:

- Note down the new size in sectors that
`pvresize`

gives you. In this case it’s 1363148800 sectors. - Start
`parted`

, set the unit to sectors, and print the partition information:

# parted /dev/vda1 unit s print

- Calculate the end sector of the partition with “start_sector + size in sectors (from
`pvresize`

above) – 1″ - Actually resize the partition with parted using
`resizepart PART_NUM END_SECTOR`

- Here is an example of the partition resize process:

- So far so good. Well, hopefully. Now is a good time to check the filesystem again and boot into your normal VM OS to see everything is still working. The reduced size should now be visible everywhere – except that the disk is still larger than the partitions on it.

`qemu-img`

- First you need to login to the QNAP system via SSH. Then enter Q and Y to get to a normal shell environment.
- Check where the
`qemu-img`

binary is. On my QNAP, it is at`/share/CE_CACHEDEV1_DATA/.qpkg/QKVM/usr/bin/qemu-img`

. To have it available without writing the full path, create a symlink like with`ln -s /share/CE_CACHEDEV1_DATA/.qpkg/QKVM/usr/bin/qemu-img /usr/sbin/qemu-img`

. - Navigate to the folder with the disk image. Check again that the VM is turned off and there are no snapshots.
- Check that the disk image information makes sense to you with
`qemu-img info Ubuntu.img`

- Resize the disk image so that there is still enough space for all partitions with e.g.
`qemu-img resize --shrink Ubuntu.img 651G`

- This command may have to run for quite a while. You can open another SSH connection and check with
`top`

that the`qemu-img`

command is still running normally if you like.

- This command may have to run for quite a while. You can open another SSH connection and check with
- An example of the process:

# qemu-img info Ubuntu.img image: Ubuntu.img file format: qcow2 virtual size: 2 TiB (2199023255552 bytes) disk size: 1.02 TiB cluster_size: 65536 Format specific information: compat: 1.1 lazy refcounts: false refcount bits: 16 corrupt: false # qemu-img resize --shrink Ubuntu.img 651G Image resized. # qemu-img info Ubuntu.img image: Ubuntu.img file format: qcow2 virtual size: 651 GiB (699005927424 bytes) disk size: 588 GiB cluster_size: 65536 Format specific information: compat: 1.1 lazy refcounts: false

- Ideally, you are now done and can check the filesystem. This was the case for me with a disk image that only used an MBR.
- In the case of a disk image using GPT, after resizing it seemed like the disk was corrupted and booting was not possible anymore. When trying to boot my original OS, it could not find the physical volume. When starting GParted Live, I got errors like
`Invalid argument during seek for read on /dev/vda`

(from GParted)`fdisk -l /dev/vda`

gave one hint:`GPT PMBR size mismatch (old_size_xyz != new_size_abc) will be corrected by write.`

but did not list the partitions correctly. Note that`fdisk`

is not appropriate for GPT disks anyway.`gdisk -l /dev/vda`

was the most helpful. It listed the disk’s partitions correctly (after complaining) and said:`Disk size is smaller than the main header indicates`

`invalid backup GPT header`

- It makes sense that the backup/secondary GPT header and partition table were corrupted because they are at the end of the disk (which we cut off with
`qemu-img resize`

). - Everything could be fixed by copying the primary GPT header and entries (which are the front of the disk) to the end with
`sgdisk -e /dev/vda`

. This also took care of the size issues along the way.

The post Shrinking a QNAP Virtualization Station disk image appeared first on Xeve.

]]>`qemu-img`

.
- Create a backup of all data and check that it works. Messing with filesystems and disk images is a great way to corrupt your data.
- Check that there are no snapshots of the VM for which you want to manipulate the disk image (in one case I noticed a snapshot was created without me noticing it):

- Decide how large the partitions on the disk and the disk itself should be. Use
`fdisk -l`

,`gdisk -l /dev/vda`

(for GPT disks),`df -h`

or other tools.- In this example there is one LVM partition with a root and swap logical volume inside. I decided 645 GiB for the root logical volume and 650 GiB for the LVM physical volume and partition should be enough.

# fdisk -l [...] Device Boot Start End Sectors Size Id Type /dev/vda1 * 2048 4294967295 4294965248 2T 8e Linux LVM Disk /dev/mapper/nicoloud--vg-root: 2 TiB, 2197995651072 bytes, 4292960256 sectors Units: sectors of 1 * 512 = 512 bytes [...] Disk /dev/mapper/nicoloud--vg-swap_1: 976 MiB, 1023410176 bytes, 1998848 sectors [...] # df -h Filesystem Size Used Avail Use% Mounted on [...] /dev/mapper/nicoloud--vg-root 2.0T 523G 1.4T 28% /

- Now is a good time to boot the VM for which you want to manipulate the disk image into GParted Live or another similar image. This is particularly necessary when you want to shrink an ext4 filesystem. Growing it, or e.g. shrinking the physical volume when the filesystem itself is already small enough, is possible online, but it does not hurt to boot into GParted Live anyway.
- Using e.g. a Ubuntu Live image is also possible but probably needs more RAM (I only had 1 GiB available for this VM at the time).

- Resize the filesystem and logical volume in one go (ext4 is definitely supported by the LVM tools – for other filesystems you might have to check):
`lvresize --resizefs --size 645G nicoloud-vg/root`

- Of course you can also run
`e2fsck`

and`resize2fs`

(or the analogous commands for another filesystem) yourself

- Of course you can also run

- Resize the physical volume:
`pvresize --setphysicalvolumesize 650G /dev/vda1`

- Of course you need to make sure all logical volumes fit inside this.

- If
`pvresize`

gives you an error in the direction of`cannot resize to XYZ extents as later ones are allocated`

although your physical volume size would have enough space for all logical volumes, then it is possible that the current physical extents make it impossible to shrink the physical volume. This can happen if e.g. the free space is at the start or middle of the physical volume instead of concentrated at the end.- You can diagnose an issue like this with
`pvs -v --segments /dev/vda1`

and fix it with`pvmove`

. Here is an example:

- You can diagnose an issue like this with

- After fixing such issues,
`pvresize`

should work:

- Note down the new size in sectors that
`pvresize`

gives you. In this case it’s 1363148800 sectors. - Start
`parted`

, set the unit to sectors, and print the partition information:

# parted /dev/vda1 unit s print

- Calculate the end sector of the partition with “start_sector + size in sectors (from
`pvresize`

above) – 1″ - Actually resize the partition with parted using
`resizepart PART_NUM END_SECTOR`

- Here is an example of the partition resize process:

- So far so good. Well, hopefully. Now is a good time to check the filesystem again and boot into your normal VM OS to see everything is still working. The reduced size should now be visible everywhere – except that the disk is still larger than the partitions on it.

`qemu-img`

- First you need to login to the QNAP system via SSH. Then enter Q and Y to get to a normal shell environment.
- Check where the
`qemu-img`

binary is. On my QNAP, it is at`/share/CE_CACHEDEV1_DATA/.qpkg/QKVM/usr/bin/qemu-img`

. To have it available without writing the full path, create a symlink like with`ln -s /share/CE_CACHEDEV1_DATA/.qpkg/QKVM/usr/bin/qemu-img /usr/sbin/qemu-img`

. - Navigate to the folder with the disk image. Check again that the VM is turned off and there are no snapshots.
- Check that the disk image information makes sense to you with
`qemu-img info Ubuntu.img`

- Resize the disk image so that there is still enough space for all partitions with e.g.
`qemu-img resize --shrink Ubuntu.img 651G`

- This command may have to run for quite a while. You can open another SSH connection and check with
`top`

that the`qemu-img`

command is still running normally if you like.

- This command may have to run for quite a while. You can open another SSH connection and check with
- An example of the process:

# qemu-img info Ubuntu.img image: Ubuntu.img file format: qcow2 virtual size: 2 TiB (2199023255552 bytes) disk size: 1.02 TiB cluster_size: 65536 Format specific information: compat: 1.1 lazy refcounts: false refcount bits: 16 corrupt: false # qemu-img resize --shrink Ubuntu.img 651G Image resized. # qemu-img info Ubuntu.img image: Ubuntu.img file format: qcow2 virtual size: 651 GiB (699005927424 bytes) disk size: 588 GiB cluster_size: 65536 Format specific information: compat: 1.1 lazy refcounts: false

- Ideally, you are now done and can check the filesystem. This was the case for me with a disk image that only used an MBR.
- In the case of a disk image using GPT, after resizing it seemed like the disk was corrupted and booting was not possible anymore. When trying to boot my original OS, it could not find the physical volume. When starting GParted Live, I got errors like
`Invalid argument during seek for read on /dev/vda`

(from GParted)`fdisk -l /dev/vda`

gave one hint:`GPT PMBR size mismatch (old_size_xyz != new_size_abc) will be corrected by write.`

but did not list the partitions correctly. Note that`fdisk`

is not appropriate for GPT disks anyway.`gdisk -l /dev/vda`

was the most helpful. It listed the disk’s partitions correctly (after complaining) and said:`Disk size is smaller than the main header indicates`

`invalid backup GPT header`

- It makes sense that the backup/secondary GPT header and partition table were corrupted because they are at the end of the disk (which we cut off with
`qemu-img resize`

). - Everything could be fixed by copying the primary GPT header and entries (which are the front of the disk) to the end with
`sgdisk -e /dev/vda`

. This also took care of the size issues along the way.

The post Shrinking a QNAP Virtualization Station disk image appeared first on Xeve.

]]>Priority queues are a useful data structure e.g. when you repeatedly need to access the largest or smallest element of a set of values but want more efficiency than keeping an array ordered. The C++ standard library contains `std::priority_queue`

, which works well for basic use cases. It offers insertion of elements as well as access and removal of the minimum (or maximum). It does *not*, however, offer a method for changing the priority of a value, which is needed for some applications (like Dijkstra’s algorithm to find shortest paths in graphs). Often such a method is called `decreaseKey`

or `changeKey`

. This post offers an implementation using a binary heap which supports this operation in `O(log(n))`

time.

The implementation makes it necessary to refer to specific elements with a numeric ID (or index) between 0 and `maxSize-1`

. This maximum size of the priority queue must be specified when calling the constructor. The priority of IDs is specified with keys, which can be both simple numeric values (e.g. integers or floating point values) or custom classes for which `operator<`

(for `IndexedMaxPriorityQueue`

) or `operator>`

(for `IndexedMinPriorityQueue`

) is defined. The following main methods are available:

`insert`

to add a new ID`peekFirst`

and`removeFirst`

to view or remove the value with minimum/maximum priority`remove`

to delete a specific ID`changeKey`

to adapt the priority of an ID`empty`

and`contains`

to check for emptiness and whether a specific ID is part of the priority queue

The code needs C++17 and is available here: https://github.com/nspo/graphs-cpp/blob/master/general/include/IndexedPriorityQueue.h

You can also check out the `test`

folder for some exampes.

The post Indexed Priority Queue in C++ appeared first on Xeve.

]]>Priority queues are a useful data structure e.g. when you repeatedly need to access the largest or smallest element of a set of values but want more efficiency than keeping an array ordered. The C++ standard library contains `std::priority_queue`

, which works well for basic use cases. It offers insertion of elements as well as access and removal of the minimum (or maximum). It does *not*, however, offer a method for changing the priority of a value, which is needed for some applications (like Dijkstra’s algorithm to find shortest paths in graphs). Often such a method is called `decreaseKey`

or `changeKey`

. This post offers an implementation using a binary heap which supports this operation in `O(log(n))`

time.

The implementation makes it necessary to refer to specific elements with a numeric ID (or index) between 0 and `maxSize-1`

. This maximum size of the priority queue must be specified when calling the constructor. The priority of IDs is specified with keys, which can be both simple numeric values (e.g. integers or floating point values) or custom classes for which `operator<`

(for `IndexedMaxPriorityQueue`

) or `operator>`

(for `IndexedMinPriorityQueue`

) is defined. The following main methods are available:

`insert`

to add a new ID`peekFirst`

and`removeFirst`

to view or remove the value with minimum/maximum priority`remove`

to delete a specific ID`changeKey`

to adapt the priority of an ID`empty`

and`contains`

to check for emptiness and whether a specific ID is part of the priority queue

The code needs C++17 and is available here: https://github.com/nspo/graphs-cpp/blob/master/general/include/IndexedPriorityQueue.h

You can also check out the `test`

folder for some exampes.

The post Indexed Priority Queue in C++ appeared first on Xeve.

]]>Disjoint-set data structures are useful in cases where you have a (possibly very large) number of elements and want to repeatedly execute two operations: marking two elements as connected, and checking whether two elements are connected. A query of the latter kind does not necessarily mean that the two elements *p* and *q* were explicitly connected to each other by the caller – it is possible, e.g., that *p* was first connected to the element *r*, and only much later in time *q* was also connected to *r*.

The disjoint sets in the data structure are commonly represented by trees, where each node in the tree contains a link to its *parent*, the parent in turn contains a link to the parent’s parent, and so on. The node at the root of the tree contains contains a link to itself, i.e. it is its own parent. All nodes of a tree are in the same set. It can be easily checked whether two elements are in the same set by evaluating whether they have the same root. The set of trees is called the disjoint-set forest. While this approach is common and can be implemented efficiently, the C++ standard library does not contain an implementation – this post tries to offer one.

Let us assume the data structure is used for N elements, each having an index between 0 and N-1. The links to the parent nodes of all elements can then easily be saved in an array of length N. If `parent[p] == q`

, then q is the parent of p. Whenever two sets containing the elements *p* and *q* should be merged, this can be achieved by attaching the root of one element to the root of the other.

The interface of the class `DisjointSets`

is simple. The constructor takes a number of elements N and creates N disjoint sets:

`DisjointSets disjointSets(20);`

Each element can then be referenced with an index between 0 and N-1. To connect the two sets containing elements *p* and *q*, you execute:

`disjointSets.setUnion(p, q);`

To check whether *p* and *q* are in the same set:

`if (disjointSets.connected(p, q)) { // ...`

Additionally, the C++ code contains a public `size()`

function which returns the number of elements (not the number of disjoint sets).

To ensure fast access and that the implicit trees do not become too high, two optimizations are necessary (though the details can vary): considering the size of the subtrees when joining two sets (using ranks or weights), and making sure that we flatten a tree whenever we step through it (using path compression or path halving).

The rank of a node is a heuristic used when joining two sets. It is an upper bound on the height of the node, which means an upper bound on the number of edges that would have to be crossed to go downwards until a leaf is reached. If a node *p* has rank 1, there will be either zero or one layer of nodes below it which have *p* as their parent. No nodes will have a node in the (potential) layer below *p* as *their* parent. The initial rank of a node which is not connected to any other node is 0. When the sets containing two nodes *p* and *q* are to be joined, the roots of both must be identified (say *rp* and *rq* respectively). For the next step, there are two possibilities:

- If one root (say
*rp*) has a lower rank than the other one (*rq*), then*rq*is set as the parent of*rp*. The ranks do not change. This is intended to avoid attaching subtrees with a high number of nodes below them to a node with a lower height. - If both roots have equal rank, then one root is arbitrarily chosen as parent and its rank is increased by 1.

One central private method of the implementation will be `findRoot`

, which will return the root element for a given input element. As mentioned above, it is beneficial to have trees with a low height. If that is the case, the root of each element can be found quickly. The ideal situation would be if all nodes which are not root nodes would point directly to their root. Path compression is a simple technique that tries to get close to this: each time a path from some kind of node to a root is traversed, the structure is modified so that the traversed nodes point directly to the root. Using recursion, this can be achieved with minimal extra code.

The two aforementioned optimizations lead to operations being possible in nearly constant amortized time. Actually, the time complexity depends on number of union operations and grows with the inverse of the Ackermann function – but the result of this inverse is smaller than or equal to 4 for any reasonable input.

The source code is available as an independent, header-only class in my Github repository related to weighted graphs (as this data structure is helpful e.g. for identifying a Minimum Spanning Tree of a weighted graph):

https://github.com/nspo/graphs-cpp/blob/master/general/include/DisjointSets.h

Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2009). Data Structures for Disjoint Sets. In *Introduction to algorithms*. Cambridge (Inglaterra): Mit Press.

The post Efficient Union-Find in C++ – or: Disjoint-set forests with Path Compression and Ranks appeared first on Xeve.

]]>Disjoint-set data structures are useful in cases where you have a (possibly very large) number of elements and want to repeatedly execute two operations: marking two elements as connected, and checking whether two elements are connected. A query of the latter kind does not necessarily mean that the two elements *p* and *q* were explicitly connected to each other by the caller – it is possible, e.g., that *p* was first connected to the element *r*, and only much later in time *q* was also connected to *r*.

The disjoint sets in the data structure are commonly represented by trees, where each node in the tree contains a link to its *parent*, the parent in turn contains a link to the parent’s parent, and so on. The node at the root of the tree contains contains a link to itself, i.e. it is its own parent. All nodes of a tree are in the same set. It can be easily checked whether two elements are in the same set by evaluating whether they have the same root. The set of trees is called the disjoint-set forest. While this approach is common and can be implemented efficiently, the C++ standard library does not contain an implementation – this post tries to offer one.

Let us assume the data structure is used for N elements, each having an index between 0 and N-1. The links to the parent nodes of all elements can then easily be saved in an array of length N. If `parent[p] == q`

, then q is the parent of p. Whenever two sets containing the elements *p* and *q* should be merged, this can be achieved by attaching the root of one element to the root of the other.

The interface of the class `DisjointSets`

is simple. The constructor takes a number of elements N and creates N disjoint sets:

`DisjointSets disjointSets(20);`

Each element can then be referenced with an index between 0 and N-1. To connect the two sets containing elements *p* and *q*, you execute:

`disjointSets.setUnion(p, q);`

To check whether *p* and *q* are in the same set:

`if (disjointSets.connected(p, q)) { // ...`

Additionally, the C++ code contains a public `size()`

function which returns the number of elements (not the number of disjoint sets).

To ensure fast access and that the implicit trees do not become too high, two optimizations are necessary (though the details can vary): considering the size of the subtrees when joining two sets (using ranks or weights), and making sure that we flatten a tree whenever we step through it (using path compression or path halving).

The rank of a node is a heuristic used when joining two sets. It is an upper bound on the height of the node, which means an upper bound on the number of edges that would have to be crossed to go downwards until a leaf is reached. If a node *p* has rank 1, there will be either zero or one layer of nodes below it which have *p* as their parent. No nodes will have a node in the (potential) layer below *p* as *their* parent. The initial rank of a node which is not connected to any other node is 0. When the sets containing two nodes *p* and *q* are to be joined, the roots of both must be identified (say *rp* and *rq* respectively). For the next step, there are two possibilities:

- If one root (say
*rp*) has a lower rank than the other one (*rq*), then*rq*is set as the parent of*rp*. The ranks do not change. This is intended to avoid attaching subtrees with a high number of nodes below them to a node with a lower height. - If both roots have equal rank, then one root is arbitrarily chosen as parent and its rank is increased by 1.

One central private method of the implementation will be `findRoot`

, which will return the root element for a given input element. As mentioned above, it is beneficial to have trees with a low height. If that is the case, the root of each element can be found quickly. The ideal situation would be if all nodes which are not root nodes would point directly to their root. Path compression is a simple technique that tries to get close to this: each time a path from some kind of node to a root is traversed, the structure is modified so that the traversed nodes point directly to the root. Using recursion, this can be achieved with minimal extra code.

The two aforementioned optimizations lead to operations being possible in nearly constant amortized time. Actually, the time complexity depends on number of union operations and grows with the inverse of the Ackermann function – but the result of this inverse is smaller than or equal to 4 for any reasonable input.

The source code is available as an independent, header-only class in my Github repository related to weighted graphs (as this data structure is helpful e.g. for identifying a Minimum Spanning Tree of a weighted graph):

https://github.com/nspo/graphs-cpp/blob/master/general/include/DisjointSets.h

Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2009). Data Structures for Disjoint Sets. In *Introduction to algorithms*. Cambridge (Inglaterra): Mit Press.

The post Efficient Union-Find in C++ – or: Disjoint-set forests with Path Compression and Ranks appeared first on Xeve.

]]>In this post, we will look at determining the system equations of a robot with two degrees of freedom in stace-space form. These state-space equations can be used for simulation and for developing a nonlinear model predictive controller. For some transformation tasks, Python with SymPy will be utilized and an alternative using the MATLAB Symbolic Math Toolbox will be shown.

During the following, we are considering a robot with two links, two masses at the end of the links, and motors applying torque as control input at the joints. A diagram of the robot is shown below.

The set of nonlinear differential equations describing the dynamic behavior of a robot with joint angle vector is often given in following form (or similarly; see e.g. *Robot Modeling and Control* by Spong et al.):

In this equation, is the mass or inertia matrix, is the coriolis matrix, and is the gravity vector. is the vector of control input torques applied at the joints.

Determining the relevant matrices of the equation above for the considered robot is possible through multiple methods. A commonly used one is applying the Lagrangian method. For this, the kinetic energy and the potential energy of the robot system must first be calculated using the generalized coordinates . The Lagrangian is then given by . The control input torque at each joint can be seen as a generalized force . Now, one differential equation for each generalized coordinate can be found by using Lagrange’s equation of motion of the second kind:

For an equivalent robot system, the calculation steps are shown in *Modeling of 2-DOF Robot Arm and Control* by Okubanjo et al. Note, however, that there are some inaccuracies in this paper: Equation 13 is incorrect due to two missing squared operators. This is corrected from equation 15 onwards. Furthermore, the matrix form of the differential equation shown in equation 38 contains an additional after the coriolis matrix. While this is generally not incorrect and an alternative form of the convention, the results in equation 34 and 35 do not fit to this form. Due to these problems, using the results from this paper cannot be recommended without double-checking. Bingül et al. (*A Fuzzy Logic Controller tuned with PSO for 2 DOF robot trajectory control*) considered the same robot configuration though, and with their results the calculations should lead to the following matrices and vectors (for the aforementioned matrix formulation of the robot system equation):

These equations cannot be fed directly into CasADi though – we need a state-space representation. An obvious choice of a state vector for this system would be the two joint angles and angular velocities (). Due to the relation of a joint angle and its angular velocity, two rows of the system equation will simply be

The other two rows for and can be calculated from the set of nonlinear differential equations above. This transformation can either be done by hand, or by using a tool like SymPy or the MATLAB Symbolic Math Toolbox. For this, all symbolic variables must first be defined, then the equations need to be defined, and finally it is possible to tell the tool to solve for the variables of interest.

Code for Python / SymPy:

#!/usr/bin/env python3 from sympy import * # masses of robot arms m1, m2 = symbols("m1 m2") # lengths of robot arms l1, l2 = symbols("l1 l2") # acceleration due to gravity g = symbols("g") # angles at robot joints q1, q2 = symbols("q1 q2") # 1st derivatives of angles dq1, dq2 = symbols("dq1 dq2") # 2nd derivatives of angles ddq1, ddq2 = symbols("ddq1 ddq2") ddq = Matrix([ddq1, ddq2]) # control input torque u1, u2 = symbols("u1 u2") U = Matrix([u1, u2]) # matrix coefficients for intertia matrix M(q) M11 = (m1+m2)*l1**2 + m2*l2**2 + 2*m2*l1*l2*cos(q2) M12 = m2*l2**2 + m2*l1*l2*cos(q2) M21 = m2*l2**2 + m2*l1*l2*cos(q2) M22 = m2*l2**2 M = Matrix([[M11, M12], [M21, M22]]) # matrix coefficients for coriolis matrix C(q, dq) C11 = -m2*l1*l2*(2*dq1*dq2 + dq2**2)*sin(q2) C21 = m2*l1*l2*dq1**2*sin(q2) C = Matrix([C11, C21]) # matrix coefficients for gravity vector G(q) G11 = (m1 + m2)*g*l1*cos(q1) + m2*g*l2*cos(q1+q2) G21 = m2*g*l2*cos(q1+q2) G = Matrix([G11, G21]) # dynamic equations of robot eq = Eq(U, M*ddq + C + G) sol = solve(eq, (ddq1, ddq2)) print("ddq1 = {} \n\nddq2 = {}".format(sol[ddq1], sol[ddq2]))

This yields the missing two lines of the state-space system equation:

> python3 transform_equations_2dof.py ddq1 = (l2*(2*dq1*dq2*l1*l2*m2*sin(q2) + dq2**2*l1*l2*m2*sin(q2) - g*l1*m1*cos(q1) - g*l1*m2*cos(q1) - g*l2*m2*cos(q1 + q2) + u1) + (l1*cos(q2) + l2)*(dq1**2*l1*l2*m2*sin(q2) + g*l2*m2*cos(q1 + q2) - u2))/(l1**2*l2*(m1 + m2*sin(q2)**2)) ddq2 = -(l2*m2*(l1*cos(q2) + l2)*(2*dq1*dq2*l1*l2*m2*sin(q2) + dq2**2*l1*l2*m2*sin(q2) - g*l1*m1*cos(q1) - g*l1*m2*cos(q1) - g*l2*m2*cos(q1 + q2) + u1) + (dq1**2*l1*l2*m2*sin(q2) + g*l2*m2*cos(q1 + q2) - u2)*(l1**2*m1 + l1**2*m2 + 2*l1*l2*m2*cos(q2) + l2**2*m2))/(l1**2*l2**2*m2*(m1 + m2*sin(q2)**2))

Code for MATLAB / Symbolic Math Toolbox (this script can also be run with Octave, which then uses SymPy in the background):

% masses of robot arms syms m1 m2; % lengths of robot arms syms l1 l2; % acceleration due to gravity syms g; % angles at robot joints syms q1 q2; % 1st derivatives of angles syms dq1 dq2; % 2nd derivatives of angles syms ddq1 ddq2; ddq = [ddq1; ddq2]; % control input torque syms u1 u2; U = [u1; u2]; % matrix coefficients for inertia matrix M(q) M11 = (m1+m2)*l1^2 + m2*l2^2 + 2*m2*l1*l2*cos(q2); M12 = m2*l2^2 + m2*l1*l2*cos(q2); M21 = m2*l2^2 + m2*l1*l2*cos(q2); M22 = m2*l2^2; M = [M11, M12; M21, M22]; % matrix coefficients for coriolis matrix C(q, dq) C11 = -m2*l1*l2*(2*dq1*dq2 + dq2^2)*sin(q2); C21 = m2*l1*l2*dq1^2*sin(q2); C = [C11; C21]; % matrix coefficients for gravity vector G(q) G11 = (m1 + m2)*g*l1*cos(q1) + m2*g*l2*cos(q1+q2); G21 = m2*g*l2*cos(q1+q2); G = [G11; G21]; % dynamic equations of robot eq = M*ddq + C + G == U; sol = solve(eq, [ddq1, ddq2]); disp(ddq1 == sol.ddq1); disp(''); disp(ddq2 == sol.ddq2);

The post NMPC with CasADi and Python – Part 3: State-space equations of a 2-DOF robot with SymPy or MATLAB appeared first on Xeve.

]]>In this post, we will look at determining the system equations of a robot with two degrees of freedom in stace-space form. These state-space equations can be used for simulation and for developing a nonlinear model predictive controller. For some transformation tasks, Python with SymPy will be utilized and an alternative using the MATLAB Symbolic Math Toolbox will be shown.

During the following, we are considering a robot with two links, two masses at the end of the links, and motors applying torque as control input at the joints. A diagram of the robot is shown below.

The set of nonlinear differential equations describing the dynamic behavior of a robot with joint angle vector is often given in following form (or similarly; see e.g. *Robot Modeling and Control* by Spong et al.):

In this equation, is the mass or inertia matrix, is the coriolis matrix, and is the gravity vector. is the vector of control input torques applied at the joints.

Determining the relevant matrices of the equation above for the considered robot is possible through multiple methods. A commonly used one is applying the Lagrangian method. For this, the kinetic energy and the potential energy of the robot system must first be calculated using the generalized coordinates . The Lagrangian is then given by . The control input torque at each joint can be seen as a generalized force . Now, one differential equation for each generalized coordinate can be found by using Lagrange’s equation of motion of the second kind:

For an equivalent robot system, the calculation steps are shown in *Modeling of 2-DOF Robot Arm and Control* by Okubanjo et al. Note, however, that there are some inaccuracies in this paper: Equation 13 is incorrect due to two missing squared operators. This is corrected from equation 15 onwards. Furthermore, the matrix form of the differential equation shown in equation 38 contains an additional after the coriolis matrix. While this is generally not incorrect and an alternative form of the convention, the results in equation 34 and 35 do not fit to this form. Due to these problems, using the results from this paper cannot be recommended without double-checking. Bingül et al. (*A Fuzzy Logic Controller tuned with PSO for 2 DOF robot trajectory control*) considered the same robot configuration though, and with their results the calculations should lead to the following matrices and vectors (for the aforementioned matrix formulation of the robot system equation):

These equations cannot be fed directly into CasADi though – we need a state-space representation. An obvious choice of a state vector for this system would be the two joint angles and angular velocities (). Due to the relation of a joint angle and its angular velocity, two rows of the system equation will simply be

The other two rows for and can be calculated from the set of nonlinear differential equations above. This transformation can either be done by hand, or by using a tool like SymPy or the MATLAB Symbolic Math Toolbox. For this, all symbolic variables must first be defined, then the equations need to be defined, and finally it is possible to tell the tool to solve for the variables of interest.

Code for Python / SymPy:

#!/usr/bin/env python3 from sympy import * # masses of robot arms m1, m2 = symbols("m1 m2") # lengths of robot arms l1, l2 = symbols("l1 l2") # acceleration due to gravity g = symbols("g") # angles at robot joints q1, q2 = symbols("q1 q2") # 1st derivatives of angles dq1, dq2 = symbols("dq1 dq2") # 2nd derivatives of angles ddq1, ddq2 = symbols("ddq1 ddq2") ddq = Matrix([ddq1, ddq2]) # control input torque u1, u2 = symbols("u1 u2") U = Matrix([u1, u2]) # matrix coefficients for intertia matrix M(q) M11 = (m1+m2)*l1**2 + m2*l2**2 + 2*m2*l1*l2*cos(q2) M12 = m2*l2**2 + m2*l1*l2*cos(q2) M21 = m2*l2**2 + m2*l1*l2*cos(q2) M22 = m2*l2**2 M = Matrix([[M11, M12], [M21, M22]]) # matrix coefficients for coriolis matrix C(q, dq) C11 = -m2*l1*l2*(2*dq1*dq2 + dq2**2)*sin(q2) C21 = m2*l1*l2*dq1**2*sin(q2) C = Matrix([C11, C21]) # matrix coefficients for gravity vector G(q) G11 = (m1 + m2)*g*l1*cos(q1) + m2*g*l2*cos(q1+q2) G21 = m2*g*l2*cos(q1+q2) G = Matrix([G11, G21]) # dynamic equations of robot eq = Eq(U, M*ddq + C + G) sol = solve(eq, (ddq1, ddq2)) print("ddq1 = {} \n\nddq2 = {}".format(sol[ddq1], sol[ddq2]))

This yields the missing two lines of the state-space system equation:

> python3 transform_equations_2dof.py ddq1 = (l2*(2*dq1*dq2*l1*l2*m2*sin(q2) + dq2**2*l1*l2*m2*sin(q2) - g*l1*m1*cos(q1) - g*l1*m2*cos(q1) - g*l2*m2*cos(q1 + q2) + u1) + (l1*cos(q2) + l2)*(dq1**2*l1*l2*m2*sin(q2) + g*l2*m2*cos(q1 + q2) - u2))/(l1**2*l2*(m1 + m2*sin(q2)**2)) ddq2 = -(l2*m2*(l1*cos(q2) + l2)*(2*dq1*dq2*l1*l2*m2*sin(q2) + dq2**2*l1*l2*m2*sin(q2) - g*l1*m1*cos(q1) - g*l1*m2*cos(q1) - g*l2*m2*cos(q1 + q2) + u1) + (dq1**2*l1*l2*m2*sin(q2) + g*l2*m2*cos(q1 + q2) - u2)*(l1**2*m1 + l1**2*m2 + 2*l1*l2*m2*cos(q2) + l2**2*m2))/(l1**2*l2**2*m2*(m1 + m2*sin(q2)**2))

Code for MATLAB / Symbolic Math Toolbox (this script can also be run with Octave, which then uses SymPy in the background):

% masses of robot arms syms m1 m2; % lengths of robot arms syms l1 l2; % acceleration due to gravity syms g; % angles at robot joints syms q1 q2; % 1st derivatives of angles syms dq1 dq2; % 2nd derivatives of angles syms ddq1 ddq2; ddq = [ddq1; ddq2]; % control input torque syms u1 u2; U = [u1; u2]; % matrix coefficients for inertia matrix M(q) M11 = (m1+m2)*l1^2 + m2*l2^2 + 2*m2*l1*l2*cos(q2); M12 = m2*l2^2 + m2*l1*l2*cos(q2); M21 = m2*l2^2 + m2*l1*l2*cos(q2); M22 = m2*l2^2; M = [M11, M12; M21, M22]; % matrix coefficients for coriolis matrix C(q, dq) C11 = -m2*l1*l2*(2*dq1*dq2 + dq2^2)*sin(q2); C21 = m2*l1*l2*dq1^2*sin(q2); C = [C11; C21]; % matrix coefficients for gravity vector G(q) G11 = (m1 + m2)*g*l1*cos(q1) + m2*g*l2*cos(q1+q2); G21 = m2*g*l2*cos(q1+q2); G = [G11; G21]; % dynamic equations of robot eq = M*ddq + C + G == U; sol = solve(eq, [ddq1, ddq2]); disp(ddq1 == sol.ddq1); disp(''); disp(ddq2 == sol.ddq2);

The post NMPC with CasADi and Python – Part 3: State-space equations of a 2-DOF robot with SymPy or MATLAB appeared first on Xeve.

]]>In this post, we will try to simulate an uncontrolled system with a forward Euler and a 4th order Runge-Kutta integration method. The latter can be the base for future closed-loop simulations.

As in the previous post, we will use a file `ode.py`

to describe the system itself:

import casadi def ode2(t, x, u): dx0 = casadi.tan(x[3]) * x[1] + 2*u[1] - 1 dx1 = x[0] - casadi.sin(x[1]) dx2 = 13 * x[3] + u[0] + 1 dx3 = 0.2*x[0] return casadi.vertcat(dx0, dx1, dx2, dx3)

As we want to do numerical integration, we need to use some kind of integration method. For this, we could either use some library or implement one ourselves. As it’s not very complicated, we’ll do the latter. The integration methods will be defined in a file `integration.py`

.

The simplest and first integration method we will implement is the Euler forward method. This method only uses a single evaluation of the derivative, i.e. it’s a first-order method:

with describing the step size. This can easily be implemented in Python:

def eulerforward(ode, h, t, x, u): xdot = ode(t, x, u) return x + xdot*h

We can test it and pass `ode2`

as argument with arbitrary state and control input values:

>>> from integration import eulerforward >>> from ode import ode2 >>> eulerforward(ode2, 0.1, 0, [0,1,1,1], [1,1]) DM([0.255741, 0.915853, 2.5, 1])

Now let’s create a `simulation.py`

file that actually simulates our system for multiple steps. We’ll use numpy for some convenience functions. Let’s define a few parameters:

import numpy as np from integration import eulerforward from ode import * # parameters t_0 = 0. # start time t_end = 1. # end time h = 0.01 # step size t_arr = np.arange(t_0, t_end, h) # array of integration times n_steps = len(t_arr) # number of steps

We define a control input that is static for the whole simulation and an initial state:

# states / control inputs u_stat = np.array([0., 0.]) # static control input x_0 = np.array([10, -5, 0.5, 1]) # initial state

We know define a data matrix that holds the values of x at all evaluated times as columns:

x = np.zeros([len(x_0), n_steps]) # holds states at different times as columns x[:,0] = x_0 # set initial state for t_0

With the help of our integration function we can now evaluate the state for all time steps:

# simulate for i in np.arange(1,n_steps): # skip 0th step x[:,i] = eulerforward(ode2, h, t_arr[i-1], x[:,i-1], u_stat).T

The `.T`

after the function call transposes the array into a column vector so it fits into `x`

.

Great! Now the simulation is done. But it would probably be nice to see a few graphs instead of only having numbers in a matrix – so let’s use `matplotlib`

to show the states over time:

# plot results from matplotlib import pyplot as plt plt.plot(t_arr, x.T) plt.legend(["x0", "x1", "x2", "x3"]) plt.xlabel("t") plt.show()

All together:

import numpy as np from integration import eulerforward from ode import * # parameters t_0 = 0. # start time t_end = 1. # end time h = 0.01 # step size t_arr = np.arange(t_0, t_end, h) # array of integration times n_steps = len(t_arr) # number of steps # states / control inputs u_stat = np.array([0., 0.]) # static control input x_0 = np.array([10, -5, 0.5, 1]) # initial state x = np.zeros([len(x_0), n_steps]) # holds states at different times as columns x[:,0] = x_0 # set initial state for t_0 # simulate for i in np.arange(1,n_steps): # skip 0th step x[:,i] = eulerforward(ode2, h, t_arr[i-1], x[:,i-1], u_stat).T # plot results from matplotlib import pyplot as plt plt.plot(t_arr, x.T) plt.legend(["x0", "x1", "x2", "x3"]) plt.xlabel("t") plt.show()

And you should see the following graph when you execute `simulation.py`

:

Not too shabby, right? Of course it’s only the behavior of an uncontrolled system, but a controller will get developed in later posts. First, let’s implement the second integration method: a Runge-Kutta 4th order integrator.

The Runge-Kutta 4th order method (RK4) uses four evaluations of the derivative and is used in many places. In general, a better approximation of the true integral can be expected than with methods of a lower order. The formulas can be found here. The implementation in `integration.py`

can be done similar to the Euler forward method:

def rk4(ode, h, t, x, u): k1 = ode(t,x,u) k2 = ode(t+h/2, x+h/2*k1, u) k3 = ode(t+h/2, x+h/2*k2, u) k4 = ode(t+h, x+h*k3, u) return x + h/6*(k1 + 2*k2 + 2*k3 + k4)

Let’s test it on the Python console:

>>> from integration import * >>> from ode import * >>> t = 0 >>> x = [1, 2, 3, 4] >>> u = [0, 0] >>> rk4(ode2, 0.01, t, x, u) DM([1.01321, 2.00097, 3.53013, 4.00201]) >>> eulerforward(ode2, 0.01, t, x, u) DM([1.01316, 2.00091, 3.53, 4.002])

So, the results seem to be similar but slightly different. Let’s replace `eulerforward`

by `rk4`

in `simulation.py`

and execute the script. Something like the following should appear:

… which looks pretty much like before – at least to our eyes. Let’s evaluate the states with both `eulerforward`

and `rk4`

and look at the difference to see how large it is! This is pretty straight forward, so here’s the resulting `simulation.py`

and the graph of the difference over time:

import numpy as np from integration import * from ode import * # parameters t_0 = 0. # start time t_end = 1. # end time h = 0.01 # step size t_arr = np.arange(t_0, t_end, h) # array of integration times n_steps = len(t_arr) # number of steps # states / control inputs u_stat = np.array([0., 0.]) # static control input x_0 = np.array([10, -5, 0.5, 1]) # initial state # states with Euler integration x_eul = np.zeros([len(x_0), n_steps]) # holds states at different times as columns x_eul[:,0] = x_0 # set initial state for t_0 # states with RK4 integration x_rk4 = np.zeros([len(x_0), n_steps]) x_rk4[:,0] = x_0 # simulate for i in np.arange(1,n_steps): # skip 0th step # simulate with Euler x_eul[:,i] = eulerforward(ode2, h, t_arr[i-1], x_eul[:,i-1], u_stat).T # simulate with RK4 x_rk4[:,i] = rk4(ode2, h, t_arr[i-1], x_eul[:,i-1], u_stat).T # plot results from matplotlib import pyplot as plt # plot Euler results plt.plot(t_arr, x_eul.T) plt.title("States with Euler integration") plt.legend(["x0", "x1", "x2", "x3"]) plt.xlabel("t") # plot RK4 results plt.figure() # new figure plt.plot(t_arr, x_rk4.T) plt.title("States with RK4 integration") plt.legend(["x0", "x1", "x2", "x3"]) plt.xlabel("t") # plot difference between Euler and RK4 plt.figure() # new figure plt.plot(t_arr, (x_rk4 - x_eul).T) plt.title("Difference between Euler and RK4 integration") plt.legend(["x0", "x1", "x2", "x3"]) plt.xlabel("t") plt.show()

OK, we can see that the effect on one state is significantly larger than on the other states during this period of time. Whether the differences here are *large* or *small* depends on the system we simulated – as we haven’t based our example system on any real system we cannot really make a clear assessment here. Note that while the differences between the methods seem to get smaller towards the end, this is just a coincidence.

So, that’s it for today! In later posts we can have a look at actually controlling our simulated system.

The post NMPC with CasADi and Python – Part 2: Simulation of an uncontrolled system appeared first on Xeve.

]]>In this post, we will try to simulate an uncontrolled system with a forward Euler and a 4th order Runge-Kutta integration method. The latter can be the base for future closed-loop simulations.

As in the previous post, we will use a file `ode.py`

to describe the system itself:

import casadi def ode2(t, x, u): dx0 = casadi.tan(x[3]) * x[1] + 2*u[1] - 1 dx1 = x[0] - casadi.sin(x[1]) dx2 = 13 * x[3] + u[0] + 1 dx3 = 0.2*x[0] return casadi.vertcat(dx0, dx1, dx2, dx3)

As we want to do numerical integration, we need to use some kind of integration method. For this, we could either use some library or implement one ourselves. As it’s not very complicated, we’ll do the latter. The integration methods will be defined in a file `integration.py`

.

The simplest and first integration method we will implement is the Euler forward method. This method only uses a single evaluation of the derivative, i.e. it’s a first-order method:

with describing the step size. This can easily be implemented in Python:

def eulerforward(ode, h, t, x, u): xdot = ode(t, x, u) return x + xdot*h

We can test it and pass `ode2`

as argument with arbitrary state and control input values:

>>> from integration import eulerforward >>> from ode import ode2 >>> eulerforward(ode2, 0.1, 0, [0,1,1,1], [1,1]) DM([0.255741, 0.915853, 2.5, 1])

Now let’s create a `simulation.py`

file that actually simulates our system for multiple steps. We’ll use numpy for some convenience functions. Let’s define a few parameters:

import numpy as np from integration import eulerforward from ode import * # parameters t_0 = 0. # start time t_end = 1. # end time h = 0.01 # step size t_arr = np.arange(t_0, t_end, h) # array of integration times n_steps = len(t_arr) # number of steps

We define a control input that is static for the whole simulation and an initial state:

# states / control inputs u_stat = np.array([0., 0.]) # static control input x_0 = np.array([10, -5, 0.5, 1]) # initial state

We know define a data matrix that holds the values of x at all evaluated times as columns:

x = np.zeros([len(x_0), n_steps]) # holds states at different times as columns x[:,0] = x_0 # set initial state for t_0

With the help of our integration function we can now evaluate the state for all time steps:

# simulate for i in np.arange(1,n_steps): # skip 0th step x[:,i] = eulerforward(ode2, h, t_arr[i-1], x[:,i-1], u_stat).T

The `.T`

after the function call transposes the array into a column vector so it fits into `x`

.

Great! Now the simulation is done. But it would probably be nice to see a few graphs instead of only having numbers in a matrix – so let’s use `matplotlib`

to show the states over time:

# plot results from matplotlib import pyplot as plt plt.plot(t_arr, x.T) plt.legend(["x0", "x1", "x2", "x3"]) plt.xlabel("t") plt.show()

All together:

import numpy as np from integration import eulerforward from ode import * # parameters t_0 = 0. # start time t_end = 1. # end time h = 0.01 # step size t_arr = np.arange(t_0, t_end, h) # array of integration times n_steps = len(t_arr) # number of steps # states / control inputs u_stat = np.array([0., 0.]) # static control input x_0 = np.array([10, -5, 0.5, 1]) # initial state x = np.zeros([len(x_0), n_steps]) # holds states at different times as columns x[:,0] = x_0 # set initial state for t_0 # simulate for i in np.arange(1,n_steps): # skip 0th step x[:,i] = eulerforward(ode2, h, t_arr[i-1], x[:,i-1], u_stat).T # plot results from matplotlib import pyplot as plt plt.plot(t_arr, x.T) plt.legend(["x0", "x1", "x2", "x3"]) plt.xlabel("t") plt.show()

And you should see the following graph when you execute `simulation.py`

:

Not too shabby, right? Of course it’s only the behavior of an uncontrolled system, but a controller will get developed in later posts. First, let’s implement the second integration method: a Runge-Kutta 4th order integrator.

The Runge-Kutta 4th order method (RK4) uses four evaluations of the derivative and is used in many places. In general, a better approximation of the true integral can be expected than with methods of a lower order. The formulas can be found here. The implementation in `integration.py`

can be done similar to the Euler forward method:

def rk4(ode, h, t, x, u): k1 = ode(t,x,u) k2 = ode(t+h/2, x+h/2*k1, u) k3 = ode(t+h/2, x+h/2*k2, u) k4 = ode(t+h, x+h*k3, u) return x + h/6*(k1 + 2*k2 + 2*k3 + k4)

Let’s test it on the Python console:

>>> from integration import * >>> from ode import * >>> t = 0 >>> x = [1, 2, 3, 4] >>> u = [0, 0] >>> rk4(ode2, 0.01, t, x, u) DM([1.01321, 2.00097, 3.53013, 4.00201]) >>> eulerforward(ode2, 0.01, t, x, u) DM([1.01316, 2.00091, 3.53, 4.002])

So, the results seem to be similar but slightly different. Let’s replace `eulerforward`

by `rk4`

in `simulation.py`

and execute the script. Something like the following should appear:

… which looks pretty much like before – at least to our eyes. Let’s evaluate the states with both `eulerforward`

and `rk4`

and look at the difference to see how large it is! This is pretty straight forward, so here’s the resulting `simulation.py`

and the graph of the difference over time:

import numpy as np from integration import * from ode import * # parameters t_0 = 0. # start time t_end = 1. # end time h = 0.01 # step size t_arr = np.arange(t_0, t_end, h) # array of integration times n_steps = len(t_arr) # number of steps # states / control inputs u_stat = np.array([0., 0.]) # static control input x_0 = np.array([10, -5, 0.5, 1]) # initial state # states with Euler integration x_eul = np.zeros([len(x_0), n_steps]) # holds states at different times as columns x_eul[:,0] = x_0 # set initial state for t_0 # states with RK4 integration x_rk4 = np.zeros([len(x_0), n_steps]) x_rk4[:,0] = x_0 # simulate for i in np.arange(1,n_steps): # skip 0th step # simulate with Euler x_eul[:,i] = eulerforward(ode2, h, t_arr[i-1], x_eul[:,i-1], u_stat).T # simulate with RK4 x_rk4[:,i] = rk4(ode2, h, t_arr[i-1], x_eul[:,i-1], u_stat).T # plot results from matplotlib import pyplot as plt # plot Euler results plt.plot(t_arr, x_eul.T) plt.title("States with Euler integration") plt.legend(["x0", "x1", "x2", "x3"]) plt.xlabel("t") # plot RK4 results plt.figure() # new figure plt.plot(t_arr, x_rk4.T) plt.title("States with RK4 integration") plt.legend(["x0", "x1", "x2", "x3"]) plt.xlabel("t") # plot difference between Euler and RK4 plt.figure() # new figure plt.plot(t_arr, (x_rk4 - x_eul).T) plt.title("Difference between Euler and RK4 integration") plt.legend(["x0", "x1", "x2", "x3"]) plt.xlabel("t") plt.show()

OK, we can see that the effect on one state is significantly larger than on the other states during this period of time. Whether the differences here are *large* or *small* depends on the system we simulated – as we haven’t based our example system on any real system we cannot really make a clear assessment here. Note that while the differences between the methods seem to get smaller towards the end, this is just a coincidence.

So, that’s it for today! In later posts we can have a look at actually controlling our simulated system.

The post NMPC with CasADi and Python – Part 2: Simulation of an uncontrolled system appeared first on Xeve.

]]>In this post, a file describing the system equations and a script to determine a steady-state setpoint will be developed. This older post contains similar code for CasADi inside MATLAB.

Before using it, the latest version of CasADi for Python should be installed using the official installation instructions. Afterwards, it can be loaded in Python with

import casadi

Tutorials often use

from casadi import *

which leads to shorter code but in some cases might possibly create problems due to names conflicting with other modules.

We first want to create a file containing the system equations. It is usually a good idea to transform the system equations, which may be known as a set of ordinary differential equations or may yet have to be derived, into a state-space representation such that the state of the system is completely described by the state vector . The dynamics of the system can then be described with the vector function or such that

This is called the state equation. In this series, we ignore the output equation

and assume that all states of the system are known at all times.

We now create a file `ode.py`

with a function `ode(t, x, u)`

that contains our state equation. The content of `ode.py`

obviously depends on the specific system, but an example set of equations for a system with four states shall be provided here:

def ode(t, x, u): dx0 = casadi.tan(x[3]) * x[1] + 2*u[1] - 1 dx1 = x[0] - casadi.sin(x[1]) dx2 = 13 * x[3] + u[0] + 1 dx3 = 2*x[0]

On each content line, the derivative of a specific state is defined. The return value of the function must be a vector and **cannot** simply be a Python list like in

return [dx0, dx1, dx2, dx3]

Instead, the `vertcat`

function can be used for vertical concatenation:

return casadi.vertcat(dx0, dx1, dx2, dx3)

The content of `ode.py`

should now be similar to the following:

import casadi def ode(t, x, u): dx0 = casadi.tan(x[3]) * x[1] + 2*u[1] - 1 dx1 = x[0] - casadi.sin(x[1]) dx2 = 13 * x[3] + u[0] + 1 dx3 = 2*x[0] return casadi.vertcat(dx0, dx1, dx2, dx3)

If we now were to call the function `ode`

from the python console with arbitrary arguments, we would find that it returns a CasADi-specific datatype:

>>> from ode import ode >>> t=0 >>> x=[0.1, 0.2, 0.3, 0.4] >>> u=[0.5, 0.6] >>> ode(t, x, u) DM([0.284559, -0.0986693, 6.7, 0.2])

Now, let’s try and create a file `steadystate.py`

to determine a point where our system is in a steady state. First, we import CasADi and the `ode`

function:

import casadi from ode import ode

Then, we go on to create an instance of the `Opti`

class. `Opti`

is a helper framework provided by CasADi that simplifies a lot of things when you are working with nonlinear programming.

ocp = casadi.Opti()

Next, we define symbolic variables for our states and control inputs. We have four states and two control inputs:

# define decision variables nx = 4 nu = 2 X = ocp.variable(nx,1) U = ocp.variable(nu,1)

Note that `X`

and `U`

do not actually contain numeric values but serve more as a placeholder for defining equations. Next, we define the constraints of our problem: we want to find a steady-state solution, therefore all four state derivatives should zero. This can be expressed pretty elegantly:

# define steady−state problem ocp.subject_to( ode(0,X,U) == 0)

That’s it for the problem definition! Now all that’s left is telling CasADi which solver it should use (ipopt seems to be a pretty good default choice if you don’t know any better), telling it to solve the problem, and extracting the values we want to know:

# solve ocp.solver('ipopt') SS_sol = ocp.solve() x_SS = SS_sol.value(X) u_SS = SS_sol.value(U) print("Steady state solution: \n x={} \n u={}".format(x_SS, u_SS))

The complete file `steadystate.py`

should now look like this:

import casadi from ode import ode ocp = casadi.Opti() # define decision variables nx = 4 nu = 2 X = ocp.variable(nx,1) U = ocp.variable(nu,1) # define steady−state problem ocp.subject_to( ode(0,X,U) == 0) # solve ocp.solver('ipopt') SS_sol = ocp.solve() x_SS = SS_sol.value(X) u_SS = SS_sol.value(U) print("Steady state solution: \n x={} \n u={}".format(x_SS, u_SS))

When you run the script, it will hopefully tell you lots of debug information and

EXIT: Optimal Solution Found.

If CasADi is not able to find a solution, it could for example mean that there is an error in the state equations, that CasADi needs your help by providing an initial guess, that there is some numeric problem, or that it is just unable to solve the problem at all. In our case, though, it provides the following result:

Steady state solution: x=[ 0.00000000e+00 4.91924738e-33 0.00000000e+00 -7.64705882e-02] u=[-0.00588235 0.5 ]

The post NMPC with CasADi and Python – Part 1: ODE and steady state appeared first on Xeve.

]]>In this post, a file describing the system equations and a script to determine a steady-state setpoint will be developed. This older post contains similar code for CasADi inside MATLAB.

Before using it, the latest version of CasADi for Python should be installed using the official installation instructions. Afterwards, it can be loaded in Python with

import casadi

Tutorials often use

from casadi import *

which leads to shorter code but in some cases might possibly create problems due to names conflicting with other modules.

We first want to create a file containing the system equations. It is usually a good idea to transform the system equations, which may be known as a set of ordinary differential equations or may yet have to be derived, into a state-space representation such that the state of the system is completely described by the state vector . The dynamics of the system can then be described with the vector function or such that

This is called the state equation. In this series, we ignore the output equation

and assume that all states of the system are known at all times.

We now create a file `ode.py`

with a function `ode(t, x, u)`

that contains our state equation. The content of `ode.py`

obviously depends on the specific system, but an example set of equations for a system with four states shall be provided here:

def ode(t, x, u): dx0 = casadi.tan(x[3]) * x[1] + 2*u[1] - 1 dx1 = x[0] - casadi.sin(x[1]) dx2 = 13 * x[3] + u[0] + 1 dx3 = 2*x[0]

On each content line, the derivative of a specific state is defined. The return value of the function must be a vector and **cannot** simply be a Python list like in

return [dx0, dx1, dx2, dx3]

Instead, the `vertcat`

function can be used for vertical concatenation:

return casadi.vertcat(dx0, dx1, dx2, dx3)

The content of `ode.py`

should now be similar to the following:

import casadi def ode(t, x, u): dx0 = casadi.tan(x[3]) * x[1] + 2*u[1] - 1 dx1 = x[0] - casadi.sin(x[1]) dx2 = 13 * x[3] + u[0] + 1 dx3 = 2*x[0] return casadi.vertcat(dx0, dx1, dx2, dx3)

If we now were to call the function `ode`

from the python console with arbitrary arguments, we would find that it returns a CasADi-specific datatype:

>>> from ode import ode >>> t=0 >>> x=[0.1, 0.2, 0.3, 0.4] >>> u=[0.5, 0.6] >>> ode(t, x, u) DM([0.284559, -0.0986693, 6.7, 0.2])

Now, let’s try and create a file `steadystate.py`

to determine a point where our system is in a steady state. First, we import CasADi and the `ode`

function:

import casadi from ode import ode

Then, we go on to create an instance of the `Opti`

class. `Opti`

is a helper framework provided by CasADi that simplifies a lot of things when you are working with nonlinear programming.

ocp = casadi.Opti()

Next, we define symbolic variables for our states and control inputs. We have four states and two control inputs:

# define decision variables nx = 4 nu = 2 X = ocp.variable(nx,1) U = ocp.variable(nu,1)

Note that `X`

and `U`

do not actually contain numeric values but serve more as a placeholder for defining equations. Next, we define the constraints of our problem: we want to find a steady-state solution, therefore all four state derivatives should zero. This can be expressed pretty elegantly:

# define steady−state problem ocp.subject_to( ode(0,X,U) == 0)

That’s it for the problem definition! Now all that’s left is telling CasADi which solver it should use (ipopt seems to be a pretty good default choice if you don’t know any better), telling it to solve the problem, and extracting the values we want to know:

# solve ocp.solver('ipopt') SS_sol = ocp.solve() x_SS = SS_sol.value(X) u_SS = SS_sol.value(U) print("Steady state solution: \n x={} \n u={}".format(x_SS, u_SS))

The complete file `steadystate.py`

should now look like this:

import casadi from ode import ode ocp = casadi.Opti() # define decision variables nx = 4 nu = 2 X = ocp.variable(nx,1) U = ocp.variable(nu,1) # define steady−state problem ocp.subject_to( ode(0,X,U) == 0) # solve ocp.solver('ipopt') SS_sol = ocp.solve() x_SS = SS_sol.value(X) u_SS = SS_sol.value(U) print("Steady state solution: \n x={} \n u={}".format(x_SS, u_SS))

When you run the script, it will hopefully tell you lots of debug information and

EXIT: Optimal Solution Found.

If CasADi is not able to find a solution, it could for example mean that there is an error in the state equations, that CasADi needs your help by providing an initial guess, that there is some numeric problem, or that it is just unable to solve the problem at all. In our case, though, it provides the following result:

Steady state solution: x=[ 0.00000000e+00 4.91924738e-33 0.00000000e+00 -7.64705882e-02] u=[-0.00588235 0.5 ]

The post NMPC with CasADi and Python – Part 1: ODE and steady state appeared first on Xeve.

]]>function dx = ode(t, x, u) % example ODE dx1 = tan(x(4)) * x(2) + 2*u(2) - 1; dx2 = x(1) - sin(x(2)); dx3 = 13 * x(4) + u(1) + 1; dx4 = 2*x(1); dx = [dx1; dx2; dx3; dx4]; end

If the desired setpoint is not known, a steady state setpoint could be calculated with a desired objective function J in CasADi like this:

close all clear variables clc import casadi.*; ocp = casadi.Opti(); nx = 4; nu = 2; X = ocp.variable(nx); U = ocp.variable(nu); % dynamics ocp.subject_to([0;0;0;0] == ode(0, X, U)); % add cost function to minimize input J = U(1,1)^2 + U(2,1)^2; ocp.minimize( J ); % specify solver ocp.solver( 'ipopt' ); % solve OCP sol = ocp.solve(); x_ss = sol.value(X); u_ss = sol.value(U);

The example yields the following setpoint: .

Now we can linearize the model at the setpoint using the MATLAB Symbolic Math Toolbox:

% setpoint t = 0; x=[0; 0; 0; -0.0769]; % state u=[0;0.5]; % control input % for eval later x1 = x(1); x2 = x(2); x3 = x(3); x4 = x(4); u1 = u(1); u2 = u(2); % symbolic vars x_sym = sym('x', [4 1]); u_sym = sym('u', [2 1]); ode_sym = ode(t,x_sym,u_sym); % calculate system and input matrices Jac_x = jacobian(ode_sym, x_sym); A = eval(subs(Jac_x)); Jac_u = jacobian(ode_sym, u_sym); B = eval(subs(Jac_u));

This linearization yields the system matrix

and the input matrix which could be used in a linear model approximation.

The post Linearize nonlinear state space equation in MATLAB at steady state setpoint appeared first on Xeve.

]]>function dx = ode(t, x, u) % example ODE dx1 = tan(x(4)) * x(2) + 2*u(2) - 1; dx2 = x(1) - sin(x(2)); dx3 = 13 * x(4) + u(1) + 1; dx4 = 2*x(1); dx = [dx1; dx2; dx3; dx4]; end

If the desired setpoint is not known, a steady state setpoint could be calculated with a desired objective function J in CasADi like this:

close all clear variables clc import casadi.*; ocp = casadi.Opti(); nx = 4; nu = 2; X = ocp.variable(nx); U = ocp.variable(nu); % dynamics ocp.subject_to([0;0;0;0] == ode(0, X, U)); % add cost function to minimize input J = U(1,1)^2 + U(2,1)^2; ocp.minimize( J ); % specify solver ocp.solver( 'ipopt' ); % solve OCP sol = ocp.solve(); x_ss = sol.value(X); u_ss = sol.value(U);

The example yields the following setpoint: .

Now we can linearize the model at the setpoint using the MATLAB Symbolic Math Toolbox:

% setpoint t = 0; x=[0; 0; 0; -0.0769]; % state u=[0;0.5]; % control input % for eval later x1 = x(1); x2 = x(2); x3 = x(3); x4 = x(4); u1 = u(1); u2 = u(2); % symbolic vars x_sym = sym('x', [4 1]); u_sym = sym('u', [2 1]); ode_sym = ode(t,x_sym,u_sym); % calculate system and input matrices Jac_x = jacobian(ode_sym, x_sym); A = eval(subs(Jac_x)); Jac_u = jacobian(ode_sym, u_sym); B = eval(subs(Jac_u));

This linearization yields the system matrix

and the input matrix which could be used in a linear model approximation.

The post Linearize nonlinear state space equation in MATLAB at steady state setpoint appeared first on Xeve.

]]>

function y = lvdistance(sentence1, sentence2, penalties, split_characters) %LVDISTANCE Calculate Levenshtein distance between two strings % Uses Wagner–Fischer algorithm % See https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm % % Arguments: % sentence1, sentence2: strings, e.g. 'hello world' % penalties (optional): vector of penalties for deletion, insertion and % substitution (default: [1 1 1]) % split_characters (optional): split at each character, not only at spaces % (default: false) % % GPL v3, Nicolai Spohrer <nicolai@xeve.de> switch nargin case 2 penalty_deletion = 1; penalty_insertion = 1; penalty_substitution = 1; split_characters = false; case 3 penalty_deletion = penalties(1); penalty_insertion = penalties(2); penalty_substitution = penalties(3); split_characters = false; case 4 penalty_deletion = penalties(1); penalty_insertion = penalties(2); penalty_substitution = penalties(3); otherwise error 'Invalid number of arguments'; end if split_characters % split at each character sentence1 = num2cell(sentence1); sentence2 = num2cell(sentence2); else % split at spaces sentence1 = strsplit(sentence1, ' '); sentence2 = strsplit(sentence2, ' '); end m = length(sentence1); n = length(sentence2); % distance 2D array d = zeros(m+1, n+1); for i=1:m+1 d(i, 1) = i-1; end for j=1:n+1 d(1, j) = j-1; end for j=1:n for i=1:m if strcmp(sentence1{i}, sentence2{j}) % words are equal, therefore no operation required d(i+1, j+1) = d(i, j); else % words are not equal d(i+1, j+1) = min(min(... d(i, j+1) + penalty_deletion,... % deletion d(i+1, j) + penalty_insertion),... % insertion d(i, j) + penalty_substitution); % substitution end end end y = d(m+1, n+1); end

The post Calculate Levenshtein distance in MATLAB (words or characters) with Wagner–Fischer algorithm appeared first on Xeve.

]]>function y = lvdistance(sentence1, sentence2, penalties, split_characters) %LVDISTANCE Calculate Levenshtein distance between two strings % Uses Wagner–Fischer algorithm % See https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm % % Arguments: % sentence1, sentence2: strings, e.g. 'hello world' % penalties (optional): vector of penalties for deletion, insertion and % substitution (default: [1 1 1]) % split_characters (optional): split at each character, not only at spaces % (default: false) % % GPL v3, Nicolai Spohrer <nicolai@xeve.de> switch nargin case 2 penalty_deletion = 1; penalty_insertion = 1; penalty_substitution = 1; split_characters = false; case 3 penalty_deletion = penalties(1); penalty_insertion = penalties(2); penalty_substitution = penalties(3); split_characters = false; case 4 penalty_deletion = penalties(1); penalty_insertion = penalties(2); penalty_substitution = penalties(3); otherwise error 'Invalid number of arguments'; end if split_characters % split at each character sentence1 = num2cell(sentence1); sentence2 = num2cell(sentence2); else % split at spaces sentence1 = strsplit(sentence1, ' '); sentence2 = strsplit(sentence2, ' '); end m = length(sentence1); n = length(sentence2); % distance 2D array d = zeros(m+1, n+1); for i=1:m+1 d(i, 1) = i-1; end for j=1:n+1 d(1, j) = j-1; end for j=1:n for i=1:m if strcmp(sentence1{i}, sentence2{j}) % words are equal, therefore no operation required d(i+1, j+1) = d(i, j); else % words are not equal d(i+1, j+1) = min(min(... d(i, j+1) + penalty_deletion,... % deletion d(i+1, j) + penalty_insertion),... % insertion d(i, j) + penalty_substitution); % substitution end end end y = d(m+1, n+1); end

The post Calculate Levenshtein distance in MATLAB (words or characters) with Wagner–Fischer algorithm appeared first on Xeve.

]]>