<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Nick Fisher's Blog</title>
    <description>Latest posts</description>
    <link>https://hydroxide.dev/</link>
    <atom:link href="https://hydroxide.dev/feed.xml" rel="self" type="application/rss+xml"/>
    <lastBuildDate>Sat, 03 Jan 2026 04:23:17</lastBuildDate>
    <generator>Blog Builder</generator>
    <item>
      <title>Managing 32-bit floating point precision with relative camera offsets</title>
      <link>https://hydroxide.dev/articles/32-bit-floating-point-precision-relative-camera-offsets/</link>
      <description>Filament vertex shaders expose the getWorldFromModelMatrix() function, which is used to compute the position of the vertex in 'world space': void materialVertex(inout MaterialVertexInputs materia</description>
      <pubDate>Tue, 09 Dec 2025 00:00:00</pubDate>
      <guid isPermaLink="true">https://hydroxide.dev/articles/32-bit-floating-point-precision-relative-camera-offsets/</guid>
      <content:encoded>&lt;p>Filament vertex shaders expose the &lt;code>getWorldFromModelMatrix()&lt;/code> function, which is used to compute the position of the vertex in 'world space':&lt;/p> &lt;pre>&lt;code>void materialVertex(inout MaterialVertexInputs material) { vec3 position = getPosition().xyz; material.worldPosition.xyz = (getWorldFromModelMatrix() * vec4(position, 1.0f)).xyz; } &lt;/code>&lt;/pre> &lt;p>If your material uses &lt;code>vertexDomain: object&lt;/code>, you don't need to explicitly compute this value; this has already been computed internally before materialVertex() is invoked (see &lt;code>initMaterialVertex&lt;/code> in &lt;code>surface_material_inputs.vs&lt;/code> and &lt;code>computeWorldPosition&lt;/code> in &lt;code>surface_getters.vs&lt;/code>).&lt;/p> &lt;p>&lt;img src="/assets/images/32-bit-float-precision/world_transform_correct.png" alt="Screenshot from 3D game showing a tuft of grass at the world origin" />&lt;p class="label">With an identity transform, the tuft of grass is rendered at (0,0,0) in world space, as we'd expect&lt;/p>&lt;/p> &lt;p>Since this matrix transforms the position of each vertex from model space to 'world space', this is conceptually similar to the &lt;em>model matrix&lt;/em>. However, there's one important qualification in the Filament documentation:&lt;/p> &lt;p class='quote'>“world space” in Filament's shading system does not necessarily match the API-level world space. To obtain the position of the API-level camera, custom materials can use getUserWorldFromWorldMatrix() to transform getWorldCameraPosition().&lt;/p> &lt;p>This suggests that the 'world space' position computed from &lt;code>getWorldFromModelMatrix&lt;/code> is not the 'real' world space position of the vertex. We can verify this by omitting the world transform when setting 'material.worldPosition':&lt;/p> &lt;pre>&lt;code>void materialVertex(inout MaterialVertexInputs material) { vec3 position = getPosition().xyz; material.worldPosition.xyz = position; } &lt;/code>&lt;/pre> &lt;p>If &lt;code>material.worldPosition&lt;/code> was the 'real' world space position for the vertex, we would expect to see the tuft of grass rendered at the same position as above. That's not the case:&lt;/p> &lt;p>&lt;img src="/assets/images/32-bit-float-precision/world_transform_incorrect.png" alt="Screenshot from 3D game showing a tuft of grass rendered larger and at a different position" />&lt;/p> &lt;p>We see something quite different; in fact, the tuft of grass actually moves with the camera:&lt;/p> &lt;p>&lt;video src="/assets/images/32-bit-float-precision/world_transform_incorrect.mov" controls preload="none">&lt;/video>&lt;/p> &lt;p>This suggests that &lt;code>material.worldPosition&lt;/code> and &lt;code>getWorldFromModelMatrix()&lt;/code> are actually &lt;em>relative to the camera position&lt;/em> rather than the true world position.&lt;/p> &lt;p>Let's check the Filament source to see what's going on. &lt;code>getWorldFromModelMatrix&lt;/code> returns the value of the uniform &lt;code>object_uniforms_worldFromModelMatrix&lt;/code>, which has been set to the value of &lt;code>sceneData.elementAt&amp;lt;WORLD_TRANSFORM&amp;gt;()&lt;/code> in &lt;code>FScene::prepareVisibleRenderables&lt;/code>. This latter value itself is &lt;code>shaderWorldTransform&lt;/code> in &lt;code>FScene::prepare&lt;/code>:&lt;/p> &lt;pre>&lt;code>const mat4f shaderWorldTransform{ worldTransform * tcm.getWorldTransformAccurate(ti) }; &lt;/code>&lt;/pre> &lt;p>&lt;code>getWorldTransformAccurate()&lt;/code> returns the 'real' world transform for the renderable instance. &lt;code>worldTransform&lt;/code> was passed from &lt;code>FRenderer::renderJob&lt;/code> to &lt;code>FView::prepare&lt;/code> and &lt;code>FScene::prepare&lt;/code>, and is actually computed in &lt;code>FView::computeCameraInfo&lt;/code>:&lt;/p> &lt;pre>&lt;code>CameraInfo FView::computeCameraInfo(FEngine&amp;amp; engine) const noexcept { double3 translation; FCamera const* const camera = mViewingCamera ? mViewingCamera : mCullingCamera; if (engine.debug.view.camera_at_origin) { // this moves the camera to the origin, effectively doing all shader computations in // view-space, which improves floating point precision in the shader by staying around // zero, where fp precision is highest. This also ensures that when the camera is placed // very far from the origin, objects are still rendered and lit properly. translation = -camera-&amp;gt;getPosition(); } return { *camera, mat4{ rotation } * mat4::translation(translation) }; } &lt;/code>&lt;/pre> &lt;p>So when &lt;code>camera_at_origin&lt;/code> is true (the default), &lt;code>worldTransform&lt;/code> is actually the &lt;em>negative&lt;/em> of the camera position. Multiplying the vertex position by &lt;code>getWorldFromModelMatrix()&lt;/code> in the vertex shader actually returns the position of the vertex relative to the camera. Or, to think of it another way, the world origin is moved to the camera origin before any vertex or fragment calculations take place.&lt;/p> &lt;p>Per the documentation, we can recover the &amp;quot;true&amp;quot; world space position of the camera by multiplying &lt;code>getWorldCameraPosition()&lt;/code> by &lt;code>getUserWorldFromWorldMatrix()&lt;/code>. From &lt;code>src/ds/ColorPassDescriptorSet.cpp&lt;/code>, we see this latter value is simply the inverse of the camera world transform calculated in &lt;code>computeCameraInfo&lt;/code> above:&lt;/p> &lt;pre>&lt;code>s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldTransform)); &lt;/code>&lt;/pre> &lt;p>So the matrix returned by &lt;code>getWorldFromModelMatrix()&lt;/code> translates the vertex to the position of the camera, and &lt;code>getUserWorldFromWorldMatrix&lt;/code> moves it back. Filament's &amp;quot;world space&amp;quot; is therefore what we refer to above as &amp;quot;camera offset space&amp;quot;, and Filament's &amp;quot;API-level world space&amp;quot; is what I refer to as &amp;quot;true worldspace&amp;quot;.&lt;/p> &lt;p>The reason for this is floating point precision.&lt;/p> &lt;h2>32-bit floating point numbers&lt;/h2> &lt;p>A 32-bit number ranges from &lt;code>0000 0000 0000 0000 0000 0000 0000 0000&lt;/code> and &lt;code>00 00 00 00&lt;/code> to &lt;code>1111 1111 1111 1111 1111 1111 1111 1111&lt;/code> and &lt;code>FF FF FF FF&lt;/code> in binary and hexadecimal respectively.&lt;/p> &lt;p>If this represents an IEEE 754 floating point number, the smallest (negative) and largest (positive) finite values will be:&lt;/p> &lt;ol> &lt;li>&lt;code>1111 1111 0111 1111 1111 1111 1111 1111&lt;/code>, &lt;code>FF 7F FF FF&lt;/code> and &lt;code>-3.40282346638528859811704E+38&lt;/code>&lt;/li> &lt;li>&lt;code>0111 1111 0111 1111 1111 1111 1111 1111&lt;/code>, &lt;code>7F 7F FF FF&lt;/code> and &lt;code>3.402823466385288598117042E+38&lt;/code>&lt;/li> &lt;/ol> &lt;p>in binary, hexadecimal and scientific decimal notation respectively (or in C, -FLT_MAX and FLT_MAX). All-zeros/all-ones aren't used for FLT_MIN/FLT_MAX because some bits are reserved for infinity, signed zeros, etc.&lt;/p> &lt;p>Consider a camera positioned at (FLT_MAX, 0.0, 0.0). By decrementing the last bit of FLT_MAX (i.e. &lt;code>7F 7F FF FE&lt;/code>), we will find the closest 32-bit floating point number to FLT_MAX in the direction of the origin. This equates to &lt;code>3.402823263561192561600338E+38&lt;/code> in decimal, roughly &lt;code>2e31&lt;/code> world units away from FLT_MAX! Clearly, there is a huge loss of precision when working with large floats.&lt;/p> &lt;p>Let's look at the following triangle:&lt;/p> &lt;pre>&lt;code> (0,0.5,0) /\ / \ (-0.5,0.0,0) /____\ (0.5,0.0,0) &lt;/code>&lt;/pre> &lt;p>Imagine we want to translate this triangle by &lt;code>(10_000_000, 0, 0)&lt;/code> (or &lt;code>4B 18 96 80&lt;/code> in hex float 32). We would expect the vertices to be positioned like so:&lt;/p> &lt;pre>&lt;code> (10_000_000,0.5,0) /\ / \ (9_999_999.5,0.0,0) /____\ (10_000_000.5,0.0,0) &lt;/code>&lt;/pre> &lt;p>However, neither &lt;code>9_999_999.5&lt;/code> nor &lt;code>10_000_000.5&lt;/code> can be exactly represented as a 32-bit floating point number. In hexadecimal, the closest 32-bit floating point numbers will be at either &lt;code>(9_999_999, 0, 0)&lt;/code>/&lt;code>4B 18 96 7F&lt;/code> in the negative direction or &lt;code>(10_000_001, 0, 0)&lt;/code>/&lt;code>4B 18 96 81&lt;/code> (in the positive direction). Applying this transform will most likely round both values to &lt;code>10_000_000&lt;/code>, collapsing the triangle to a single point. We can verify this with numpy:&lt;/p> &lt;pre>&lt;code>&amp;gt;&amp;gt;&amp;gt; import numpy &amp;gt;&amp;gt;&amp;gt; numpy.float32(10_000_000) + numpy.float32(0.5) 10000000.0 &amp;gt;&amp;gt;&amp;gt; numpy.float32(10_000_000) + numpy.float32(-0.5) 10000000.0 &lt;/code>&lt;/pre> &lt;blockquote> &lt;p>Python's &lt;code>float&lt;/code> is almost always a 64-bit floating point number, so I'm using numpy to make sure I'm working with 32-bit floats.&lt;/p> &lt;/blockquote> &lt;p>Since floating point numbers have greater precision the closer you get to zero, if we're forced to work with only 32-bits, we can squeeze more precision by reducing the absolute size of the value it contains.&lt;/p> &lt;p>For example, we could adjust the model matrix so that all transforms are specified &lt;em>relative to the camera position&lt;/em>, rather than the world origin.&lt;/p> &lt;p>Imagine a camera positioned at &lt;code>(9_999_990, 0, 0)&lt;/code>, looking at our triangle above. In &amp;quot;camera offset space&amp;quot;, the transformed triangle origin is at &lt;code>(10,0,0)&lt;/code> (rather than &lt;code>(10_000_000, 0, 0)&lt;/code> in &amp;quot;real world space&amp;quot;). This leaves us with sufficient precision to render the triangle above:&lt;/p> &lt;pre>&lt;code>&amp;gt;&amp;gt;&amp;gt; numpy.float32(10) + numpy.float32(0.5) 10.5 &amp;gt;&amp;gt;&amp;gt; numpy.float32(10) + numpy.float32(-0.5) 9.5 &lt;/code>&lt;/pre> &lt;p>Returning to Filament, it should be clear that moving the world origin to the camera origin is Filament's way to maximize precision for floating point calculations.&lt;/p></content:encoded>
    </item>
    <item>
      <title>July 2025 - Open Source Update</title>
      <link>https://hydroxide.dev/articles/july-2025-update-on-open-source/</link>
      <description>Quick update regarding some of my recent open source activity. Thermion v0.3.0 is now available on pub.dev . This was the culmination of months of internal refactoring, mostly to expose more of the</description>
      <pubDate>Wed, 30 Jul 2025 16:00:00</pubDate>
      <guid isPermaLink="true">https://hydroxide.dev/articles/july-2025-update-on-open-source/</guid>
      <content:encoded>&lt;p>Quick update regarding some of my recent open source activity.&lt;/p> &lt;p>&lt;a href="https://pub.dev/packages/thermion_flutter">Thermion v0.3.0 is now available on pub.dev&lt;/a>. This was the culmination of months of internal refactoring, mostly to expose more of the public Filament API to the internal Dart API. This made it considerably easier to implement things like texture projection (which required multiple views/render targets), as well as various miscellaneous rendering options like fog, custom camera projections, and so on. This also built heavily on the &lt;a href="/articles/dart-javascript-interop-web-assembly">js_interop work I did&lt;/a>, which means the web version has now reached feature parity with the desktop &amp;amp; mobile version. Here's a quick demo of a basic game built with Thermion, running in Chrome:&lt;/p> &lt;p>&lt;video src="/assets/media/thermion_web.webm" controls>&lt;/video>&lt;/p> &lt;p>I also spent around a week to extract the timeline/keyframe system I had developed for &lt;a href="https://mixreel.ai">mixreel&lt;/a> into a standalone package:&lt;/p> &lt;p>&lt;video src="/assets/media/flutter_timeline.webm" controls>&lt;/video>&lt;/p> &lt;p>I hadn't planned to do this, but when someone asked if I knew any open source packages that did the same thing, I figured it would be a good excuse to clean up my work and abstract everything away from the mixreel internals.&lt;/p> &lt;p>I'm glad I did so, because it forced me to clean up the interfaces and fix some of things that had really been bugging me. The published version is &lt;a href="https://pub.dev/packages/flutter_keyframe_timeline">available on pub.dev here&lt;/a>. The source repository is &lt;a href="https://github.com/nmfisher/flutter_keyframe_timeline">available on GitHub here&lt;/a>.&lt;/p> &lt;p>I also quickly vibe-coded &lt;a href="https://github.com/nmfisher/dart_static_site_generator">yet another static site generator&lt;/a>. This one uses markdown/Liquify templates to output static HTML; the earlier Jaspr version was proving overkill for rendering mostly static HTML.&lt;/p></content:encoded>
    </item>
    <item>
      <title>Dart + WebAssembly with Javascript Interop</title>
      <link>https://hydroxide.dev/articles/dart-javascript-interop-web-assembly/</link>
      <description>The lack of proper web compatibility for Thermion has been bugging me for some time. This hasn't been for lack of trying - the main obstacle was that Dart doesn't have an (easy) path for direct inte</description>
      <pubDate>Fri, 13 Jun 2025 00:00:00</pubDate>
      <guid isPermaLink="true">https://hydroxide.dev/articles/dart-javascript-interop-web-assembly/</guid>
      <content:encoded>&lt;p>The lack of proper web compatibility for &lt;a href="https://thermion.dev">Thermion&lt;/a> has been bugging me for some time. This hasn't been for lack of trying - the main obstacle was that Dart doesn't have an (easy) path for direct interop with WebAsssembly libraries.&lt;/p> &lt;p>Native targets (Android, iOS, macOS, Linux and Windows) can all use &lt;code>dart:ffi&lt;/code> to invoke external functions directly and pass around native data structures and pointers to native memory. This was previously partially available when compiling to WebAssembly, but after &lt;a href="https://github.com/dart-lang/sdk/issues/37355">the public API was removed&lt;/a> in 2024, the only officially supported method for interop with WebAssembly libraries is (indirect) invocation via Javascript interop (which I'll cover in more detail below).&lt;/p> &lt;h2>Dart FFI interop for native targets&lt;/h2> &lt;p>First, let's recap the FFI approach. Say we have a header file &lt;code>my_native_func.h&lt;/code>:&lt;/p> &lt;pre>&lt;code>int my_native_function(int number); &lt;/code>&lt;/pre> &lt;p>and matching implementation &lt;code>my_native_func.c&lt;/code>:&lt;/p> &lt;pre>&lt;code>int my_native_function(int number) { return number+1; } &lt;/code>&lt;/pre> &lt;p>and we've already compiled this library to &lt;code>my_native_func.dylib&lt;/code>.&lt;/p> &lt;p>We now want to write a Dart program to load this library and invokes this method. In our &lt;code>main.dart&lt;/code>, we could write the following binding:&lt;/p> &lt;pre>&lt;code>@Native&amp;lt;Int32 Function(Pointer&amp;lt;Uint8&amp;gt;)&amp;gt;(isLeaf: true) external int my_native_function(Pointer&amp;lt;Uint8&amp;gt; addr); &lt;/code>&lt;/pre> &lt;p>and then invoke it directly in our &lt;code>main&lt;/code> function:&lt;/p> &lt;pre>&lt;code class="language-void"> // I didn't explain how to compile/link the native library // because it's not relevant to this topic, // so just assume this loads the library loadDynamicLibrary(); final value = my_native_function(0); print(&amp;quot;Value: $value&amp;quot;); } &lt;/code>&lt;/pre> &lt;p>Running &lt;code>dart run main.dart&lt;/code> will print &lt;code>Value: 1&lt;/code>.&lt;/p> &lt;pre>&lt;code>(base) nickfisher@Nicks-Mac-mini % dart run main.dart Value: 1 &lt;/code>&lt;/pre> &lt;h2>Dart JS interop for WebAssembly&lt;/h2> &lt;p>When compiling to WebAssembly, however, this won't work. In fact, it will throw a compile-time error complaining that &lt;code>dart:ffi&lt;/code> is not available for the target platform longer.&lt;/p> &lt;pre>&lt;code>(base) nickfisher@Nicks-Mac-mini % dart compile wasm main.dart main.dart:1:1: Error: 'dart:ffi' can't be imported when compiling to Wasm. import 'dart:ffi'; ^ main.dart:14:37: Error: The '.address' expression can only be used as argument to a leaf native external call. final value = my_native_function(data.address); &lt;/code>&lt;/pre> &lt;p>Luckily, Dart has very robust options for &lt;em>Javascript&lt;/em> interop (&lt;code>package:web&lt;/code> and &lt;code>dart:js_interop&lt;/code>). WebAssembly compilers generally don't just produce &lt;code>.wasm&lt;/code> modules, they can also generate Javascript wrappers to allow those WebAssembly modules to be loaded and executed in Javascript runtimes (like v8/Node/etc).&lt;/p> &lt;p>Assuming we have &lt;code>my_native_func.wasm&lt;/code> (the WebAssembly module) and &lt;code>my_native_func.wasm.js&lt;/code> (the corresponding Javascript wrapper), we can invoke our &amp;quot;native&amp;quot; function like so:&lt;/p> &lt;pre>&lt;code>@JS('my_native_function') external JSNumber my_native_function(JSNumber addr); void main(List&amp;lt;String&amp;gt; args) { // Similarly, just assume this loads the Javascript module loadDynamicLibrary(); final value = my_native_function(0.toJS); print(&amp;quot;Value: ${value.toDart}&amp;quot;); } &lt;/code>&lt;/pre> &lt;p>While this looks superficially similar to the &lt;code>dart:ffi&lt;/code> approach, there are a few key differences:&lt;/p> &lt;ul> &lt;li>the @JS annotation indicates where to find the function in the global JS namespace&lt;/li> &lt;li>rather than defining the argument/return types as a primitive Dart &lt;code>int&lt;/code>, this converts the argument and return types to/from the JS equivalent type respectively. This isn't actually necessary (if you define these as &lt;code>int&lt;/code>, the Dart compiler will take care of the conversion for you), but I have kept these in to explicitly illustrate that you are passing Javascript types around - not native WebAssembly types.&lt;/li> &lt;/ul> &lt;h2>The problems with JS interop&lt;/h2> &lt;p>If the native library only comprises a single native function, then it's no problem to do this by hand. If/when something changes in the native library, it's trivial to edit the sole JS binding manually.&lt;/p> &lt;p>A package like Thermion, though, exposes more than 500 native function definitions. While it's possible to write every single binding by hand, it's very time-consuming and tedious.&lt;/p> &lt;p>Using native FFI, this isn't a problem; the &lt;code>ffigen&lt;/code> package auto-generates bindings practically instantly. There's no equivalent for &lt;code>js_interop&lt;/code>.&lt;/p> &lt;p>What's more, FFI interop types are slightly different from JS interop types. Take the following native function definition as an example.&lt;/p> &lt;pre>&lt;code>void my_native_function(uint8_t* ptr); &lt;/code>&lt;/pre> &lt;p>With &lt;code>dart:ffi&lt;/code>, the binding is straightforward:&lt;/p> &lt;pre>&lt;code>@ffi.Native&amp;lt;ffi.Void Function(ffi.Pointer&amp;lt;Uint8&amp;gt;)&amp;gt;(isLeaf: true) external void my_native_function( ffi.Pointer&amp;lt;Uint8&amp;gt; ptr, ); &lt;/code>&lt;/pre> &lt;p>Without &lt;code>dart:ffi&lt;/code>, there's no &lt;code>Pointer&lt;/code> or &lt;code>Uint8&lt;/code> class - so how do you know which Javascript data type will be accepted/returned by your native function? You need to roll your own, which requires at least passing familiarity with the WebAssembly ABI and calling conventions. You might then also need to maintain two separate calling libraries - one for FFI, one for JS interop WebAssembly.&lt;/p> &lt;h2>jsgen to the rescue&lt;/h2> &lt;p>The obvious solution to these problems is a code-generator that does all of this for you. That's exactly what I did with &lt;a href="https://github.com/nmfisher/native/tree/jsgen/pkgs/ffigen/lib/jsgen">&lt;code>jsgen&lt;/code>&lt;/a> - forked the &lt;code>ffigen&lt;/code> package to generate JS interop bindings from C headers (and additionally, to reimplement most &lt;code>dart:ffi&lt;/code> and &lt;code>package:ffi&lt;/code> Dart types).&lt;/p> &lt;p>Where possible, I reused the interfaces and syntax from &lt;code>dart:ffi&lt;/code>; this makes it much easier to use as a drop-in replacement for bindings generated with &lt;code>ffigen&lt;/code>. For example, we only need to slightly refactor &lt;code>main.dart&lt;/code> in the above example to use conditional imports:&lt;/p> &lt;pre>&lt;code class="language-main.dart">import 'bindings.dart'; void main(List&amp;lt;String&amp;gt; args) { // I didn't explain how to compile/link the native library // because it's not relevant to this topic, // so just assume this loads the library loadDynamicLibrary(); final value = my_native_function(0); print(&amp;quot;Value: $value&amp;quot;); } &lt;/code>&lt;/pre> &lt;p>In bindings.dart:&lt;/p> &lt;pre>&lt;code>export 'bindings.ffi.g.dart' if (dart.library.io) 'bindings.ffi.g.dart' if (dart.library.js_interop) 'bindings.js.g.dart'; &lt;/code>&lt;/pre> &lt;p>In bindings.ffi.g.dart:&lt;/p> &lt;pre>&lt;code>@Native&amp;lt;Int32 Function(Pointer&amp;lt;Uint8&amp;gt;)&amp;gt;(isLeaf: true) external int my_native_function(Pointer&amp;lt;Uint8&amp;gt; addr); &lt;/code>&lt;/pre> &lt;p>In bindings.js.g.dart:&lt;/p> &lt;pre>&lt;code>@Native&amp;lt;Int32 Function(Pointer&amp;lt;Uint8&amp;gt;)&amp;gt;(isLeaf: true) external int my_native_function(Pointer&amp;lt;Uint8&amp;gt; addr); &lt;/code>&lt;/pre> &lt;p>With this approach, you only need to refactor the import paths, not every invocation of &lt;code>my_native_function&lt;/code>. This was a huge relief when migrating Thermion; I would have needed to a full day to refactor otherwise.&lt;/p> &lt;h2>Shared memory&lt;/h2> &lt;p>Unfortunately, Javascript interop isn't an exact substitute for FFI interop. Some FFI data structures don't have equivalents in Javascript. For example, Thermion often uses the following structure to pass byte data from Dart to native code:&lt;/p> &lt;pre>&lt;code>@Native&amp;lt;Void Function(Pointer&amp;lt;Uint8&amp;gt;)&amp;gt;(isLeaf: true) external void native_load(Pointer&amp;lt;Uint8&amp;gt; addr); Future loadGltf(String assetPath) async { var byteData = await rootBundle.load(assetPath); var buffer = await byteData.buffer.asUint8List(byteData.offsetInBytes); native_load(buffer.address); } &lt;/code>&lt;/pre> &lt;p>The &lt;code>.address&lt;/code> accessor and the &lt;code>isLeaf: true&lt;/code> annotation here are key. With the latter, the Dart garbage collector is guaranteed not to run before &lt;code>native_load&lt;/code> has finished executing. This ensures the memory location of the Dart &lt;code>buffer&lt;/code> object will not move while the function is executing; the Pointer derived from the &lt;code>.address&lt;/code> accessor will therefore remain valid for the lifetime of the invocation. Similarly, the &lt;code>.address&lt;/code> accessor cannot be used if the native function being invoked is &lt;em>not&lt;/em> explicitly marked as a leaf call.&lt;/p> &lt;p>js_interop has no such equivalent. &lt;code>.address&lt;/code> isn't a runtime lookup, it's actually a compiler transformation which simply isn't available when compiling Dart to Javascript or WebAssembly.&lt;/p> &lt;p>That's not all though - the whole concept doesn't make sense in the context of Javascript interop, where Dart code runs in one memory space and WebAssembly code in another. Just because we can use Javascript to communicate between the two doesn't mean that memory addresses are mutually accessible.&lt;/p> &lt;p>So conceptually, we first need to figure out how to implement some form of shared memory between Dart and WebAssembly.&lt;/p> &lt;p>Luckily, with the emscripten compiler, we can allocate memory on the stack and/or heap and pass this around as a typed Javscript array:&lt;/p> &lt;pre>&lt;code>emscripten::val emscripten_make_uint8_buffer(int length) { uint8_t *buffer = (uint8_t*)malloc(length); auto v = emscripten::val(emscripten::typed_memory_view(length, buffer)); return v; } &lt;/code>&lt;/pre> &lt;p>If you're not familiar with emscripten, here's a high level overview:&lt;/p> &lt;ul> &lt;li>malloc allocates &lt;em>length&lt;/em> bytes of memory on the emscripten heap&lt;/li> &lt;li>emscripten::typed_memory_view creates a view on the underlying buffer (so we can pass around a typed array without copying the underlying data)&lt;/li> &lt;li>emscripten:val creates a Javascript object to that can be passed directly back to Dart as a JSObject&lt;/li> &lt;/ul> &lt;p>With Dart static extensions, I can create a compatible implementation of the &lt;code>.address&lt;/code> extension on &lt;code>dart:typed_data&lt;/code> classes like so:&lt;/p> &lt;pre>&lt;code class="language-js_interop.dart">@JS('emscripten_make_uint8_buffer); external JSUint8Array emscripten_make_uint8_buffer(int length); @JS('free); external void emscripten_free(void* ptr); final _allocations = &amp;lt;Uint8List, int&amp;gt;{}; extension Uint8ListExtension on Uint8List { Pointer&amp;lt;Uint8&amp;gt; get address { if (this.lengthInBytes == 0) { return nullptr; } final jsTypedArray = emscripten_make_uint8_buffer(this.lengthInBytes); jsTypedArray.toDart.setRange(0, length, this); final ptr = Pointer&amp;lt;Uint8&amp;gt;(jsTypedArray.offsetInBytes); _allocations[this] = ptr; return ptr; } void free() { if(_allocations.contains(this)) { emscripten_free(_allocations[ptr]!); _allocations.remove(this); } } } &lt;/code>&lt;/pre> &lt;p>Accessing &lt;code>.address&lt;/code> on an instance of &lt;code>Uint8List&lt;/code> will create a JSUint8Array via emscripten, copy the contents of the Dart &lt;code>UInt8List&lt;/code> and return a pointer to the emscripten heap to the caller. This pointer can be passed directly back to a native function exactly as we do with FFI interop:&lt;/p> &lt;pre>&lt;code class="language-gltf.dart">Future loadGltf(String assetPath) async { var byteData = await rootBundle.load(assetPath); var buffer = await byteData.buffer.asUint8List(byteData.offsetInBytes); native_load(buffer.address); buffer.free(); } &lt;/code>&lt;/pre> &lt;p>The memory copy is unfortunate, but unavoidable unless you have complete control over how the &lt;code>Uint8List&lt;/code> was constructed in the first place. You also now need to manually track the lifetime of the additional native allocation, which is painful.&lt;/p> &lt;p>Until Dart fully supports direct WebAssembly interop (rather than indirect interop via Javascript), that's what we're stuck with.&lt;/p></content:encoded>
    </item>
    <item>
      <title>About Me</title>
      <link>https://hydroxide.dev/about/</link>
      <description>Currently doing a lot of backend/AI agent work, speech-to-text, text-to-speech, 3D animation. Mostly working with Dart, C++, WebAssembly, Python/PyTorch, Typescript and Blender/bpy. My projects:</description>
      <pubDate>Thu, 12 Jun 2025 16:00:00</pubDate>
      <guid isPermaLink="true">https://hydroxide.dev/about/</guid>
      <content:encoded>&lt;p>Currently doing a lot of backend/AI agent work, speech-to-text, text-to-speech, 3D animation. Mostly working with Dart, C++, WebAssembly, Python/PyTorch, Typescript and Blender/bpy.&lt;/p> &lt;p>My projects:&lt;/p> &lt;ul> &lt;li>&lt;a href="https://mixreel.ai">Mixreel&lt;/a>&lt;/li> &lt;li>&lt;a href="https://playmixworld.com">Mixworld&lt;/a>&lt;/li> &lt;li>&lt;a href="https://polyvox.app">Polyvox&lt;/a>&lt;/li> &lt;/ul> &lt;p>Get in touch via &lt;a href="https://github.com/nmfisher/">GitHub&lt;/a>, &lt;a href="https://nickfisherau.bsky.social">BlueSky&lt;/a>, &lt;a href="https://twitter.com/NickFisherAU">Twitter&lt;/a>, &lt;a href="https://mstdn.social/@nickfisherau">Mastodon&lt;/a> or &lt;a href="mailto:nick.fisher@avinium.com">e-mail&lt;/a>.&lt;/p></content:encoded>
    </item>
    <item>
      <title>Building a (Mini) 3D Flutter Game - Part 1</title>
      <link>https://hydroxide.dev/articles/building-a-mini-3d-flutter-game-engine-part-1/</link>
      <description>I've been working on flutter_filament for some time now, a package that enables cross-platform 3D rendering in Flutter apps with the Filament Physically Based Rendering library. I still haven't</description>
      <pubDate>Fri, 03 May 2024 00:00:00</pubDate>
      <guid isPermaLink="true">https://hydroxide.dev/articles/building-a-mini-3d-flutter-game-engine-part-1/</guid>
      <content:encoded>&lt;p>I've been working on &lt;a href="https://github.com/nmfisher/flutter_filament">flutter_filament&lt;/a> for some time now, a package that enables cross-platform 3D rendering in Flutter apps with the &lt;a href="https://github.com/google/filament">Filament&lt;/a> Physically Based Rendering library.&lt;/p> &lt;p>I still haven't managed to write a blog post on the package itself, but if you're interested, in the meantime you can check out &lt;a href="https://nick-fisher.com/articles/3d-pbr-with-flutter-presentation-to-singapore-flutter-meetup/">a presentation I gave at the Singapore Flutter meetup here&lt;/a> and &lt;a href="https://github.com/nmfisher/flutter_filament/issues/22#issuecomment-2071802422">this GitHub issue&lt;/a> for a high-level overview.&lt;/p> &lt;h2>From Renderer To Game&lt;/h2> &lt;p>I'm not a game developer or designer (aside from a few toy projects in my university days), but I'd been itching to extend &lt;code>flutter_filament&lt;/code> with a basic game functionality for some time now.&lt;/p> &lt;p>When Google launched a &lt;a href="https://flutter.dev/global-gamers">Flutter game competition&lt;/a> earlier in the year, it gave me a convenient excuse to set aside some time to do so.&lt;/p> &lt;p>That being said, no-one was sponsoring me to write a game, let alone a game engine. With bills to pay, I couldn't take &lt;em>too&lt;/em> much time away from paying work, so I needed to be very judicious and implement only the absolute bare minimum to make a game (hence the &amp;quot;mini&amp;quot; game).&lt;/p> &lt;p>The initial game concept was intentionally basic : paddle a canoe down a river, fish rubbish out of the water with a net. Different objects would be worth different points, and the objective would be to collect as many points as possible before reaching the end of the river.&lt;/p> &lt;p>&lt;img src="/assets/images/flutter_game_concept/flutter_game_concept1.png" alt="Screenshot from first game concept showing a character in a boat on a river with various pieces of floating rubbish" />&lt;/p> &lt;blockquote> &lt;p>You've probably realized that this is &lt;em>not&lt;/em> the game I ended up submitting for the competition. After I went through the engine work below, I started afresh with a different concept, which I'll cover in part 2.&lt;/p> &lt;/blockquote> &lt;p>The 2D UI overlay would be handled by Flutter, and I already had a renderer in flutter_filament, so I was able to render/transform 3D objects, add lights/skybox, and play animations.&lt;/p> &lt;p>What I needed to add, though, was:&lt;/p> &lt;ol> &lt;li>the ability to attach an over-the-shoulder camera&lt;/li> &lt;li>collision detection to stop the canoe itself clipping through the river banks, and to detect when an object was caught by the net&lt;/li> &lt;li>keyboard/mouse controls for moving the character and triggering the animations&lt;/li> &lt;/ol> &lt;p>(1) was very straightforward, simply because I could cheat and avoid the issue by exporting the canoe/character model from Blender with a camera node as a child. This was time away from paid work, I had no shame in taking shortcuts every which way I went!&lt;/p> &lt;h2>Collision detection&lt;/h2> &lt;p>Collision detection wasn't going to be as trivial as that, but I was hoping I could get away with something simple like the following pseudo-code:&lt;/p> &lt;pre>&lt;code>void collides(Entity entity1, Entity entity2) { // implement this } void calculateCollisions() { for(auto entity1 : scene) { for(auto entity2 : scene) { if(entity1 != entity2 &amp;amp;&amp;amp; collides(entity1, entity2)) { collisionCallback(entity1, entity2); } } } } void renderLoop() { while(true) { calculateCollisions(); render(); } } &lt;/code>&lt;/pre> &lt;p>This O(N^2) complexity in the hot path would obviously be terrible for a real game, but this concept only needed a few dozen renderable entities so I didn't expect it to be a problem.&lt;/p> &lt;p>Implementing the actual &lt;code>collides(...)&lt;/code> method didn't appear too difficult at first glance either. The Filament library (and the glTF format more generally) expose &lt;a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection#axis-aligned_bounding_boxes">axis-aligned bounding boxes&lt;/a> for assets, so I thought I could get away with something as simple as:&lt;/p> &lt;pre>&lt;code>auto aabb1 = worldTransform(entity1,entity1.aabb); auto aabb2 = worldTransform(entity2,entity2.aabb); for(auto vertex : aabb1.vertices) { if(aabb2.contains(vertex)) { return true; } } &lt;/code>&lt;/pre> &lt;p>However, there were a few problems with this simple approach.&lt;/p> &lt;p>One is that Filament uses the rest pose of the model to calculate the bounding box when it is imported. This isn't a problem for static (i.e. non-animated) models - like determining whether the canoe hit the river bed. But for a character model with a swipe animation, the bounding box remains fixed and doesn't account for the fact that the animated limb is now &amp;quot;outside&amp;quot; this box.&lt;/p> &lt;p>&lt;img src="/assets/images/flutter_game_concept/flutter_game_concept1_aabb1.jpeg" alt="Screenshot from first game concept showing the boat on top of a piece of rubbish" />&lt;/p> &lt;p>Another is that this is an &lt;em>axis-aligned&lt;/em> bounding box, meaning that the extent along each axis changes depending on the rotation of the model. This means that the AABBs can intersect, even though visually, there's no collision.&lt;/p> &lt;p>The other problem is that the bounding box of the top-level entity is (obviously) larger than the bounding box of the actual object we want to test (the end of the net). We only want to award points when the net hits the floating object in the water, not (for example) when the canoe reverses into one.&lt;/p> &lt;p>&lt;img src="/assets/images/flutter_game_concept/flutter_game_concept1_no_collision.jpeg" alt="Screenshot from first game concept showing the boat on top of a piece of rubbish" />&lt;/p> &lt;p>In keeping with the spirit of &amp;quot;do as little work as possible&amp;quot;, I avoided the issue again by attaching a number of small hidden cubes in Blender to the collidable parts (the riverbanks, the canoe and the front of the canoe where the scoop animation would intersect with the water).&lt;/p> &lt;h2>Keyboard/mouse control&lt;/h2> &lt;p>At this stage I was mostly working on the desktop (MacOS) version, so I wanted to be able to use conventional FPS controls to move the character (WASD keys for forward/back/strafe, mouse movement for look and the mouse button for the &amp;quot;swing net&amp;quot; action).&lt;/p> &lt;p>In a normal game engine, you'd expect to be able to collect/process user input inside the main loop:&lt;/p> &lt;pre>&lt;code>void main() { while(true) { processInput(); calculateCollisions(); waitForVsync(); render(); } } &lt;/code>&lt;/pre> &lt;p>This doesn't quite fit the way that &lt;code>flutter_filament&lt;/code> is structured though, where the Flutter UI loop is running on the main thread and a separate render loop running on a background thread.&lt;/p> &lt;p>There's no inherent reason why we couldn't process keyboard and mouse events in both loops. But with the Flutter framework providing tools to handle user input across all supported platforms, why reinvent the wheel?&lt;/p> &lt;p>I had already implemented basic manipulation via Flutter &lt;code>GestureDetector&lt;/code> widgets for the main 'scene' camera, so it was relatively straightforward to extend this to manipulating the camera attached to the model.&lt;/p> &lt;p>To maintain consistent movement speed (and to stop the transform updates when a collision is detected), though, I needed to queue up the user input so it was only processed once per (render) frame.&lt;/p> &lt;p>As a side note, I don't &lt;em>think&lt;/em> there's any inherent reason why I couldn't restructure &lt;code>flutter_filament&lt;/code> to run more like a conventional game loop:&lt;/p> &lt;pre>&lt;code>void main() { while(true) { processInput(); calculateCollisions(); waitForVsync(); flutterEngine.tick(); render(); } } &lt;/code>&lt;/pre> &lt;p>In this structure, the Flutter engine would render into an offscreen render target, and the game is then responsible for compositing this at the top of the scene view. This strikes me as functionally similar to the &lt;a href="https://docs.flutter.dev/add-to-app">Flutter &amp;quot;add-to-app&amp;quot; scenario&lt;/a>, so this is probably feasible - I just haven't had any compelling reason to do so yet.&lt;/p> &lt;p>In the next post, I'll go into some more detail on the work needed for the second iteration (GPU instancing, menu callbacks on entity mouseover).&lt;/p></content:encoded>
    </item>
    <item>
      <title>Static Blog Generator with Dart &amp; Jaspr</title>
      <link>https://hydroxide.dev/articles/2024-05-02-static-blog-generator-with-dart-jaspr/</link>
      <description>I was happy to recently discover the jaspr project , a Dart framework for generating dynamic and static HTML/JS pages with a pseudo-Flutter syntax. It's a far better alternative to Flutter web, at</description>
      <pubDate>Wed, 01 May 2024 16:00:00</pubDate>
      <guid isPermaLink="true">https://hydroxide.dev/articles/2024-05-02-static-blog-generator-with-dart-jaspr/</guid>
      <content:encoded>&lt;p>I was happy to recently discover the &lt;a href="https://github.com/">jaspr project&lt;/a>, a Dart framework for generating dynamic and static HTML/JS pages with a pseudo-Flutter syntax.&lt;/p> &lt;p>It's a far better alternative to Flutter web, at least for general purpose web pages or even certain basic web applications.&lt;/p> &lt;p>For one, SEO is a non-starter when it comes to Flutter web apps. Text elements are rendered straight to a canvas, so search engine crawlers will have a hard time indexing content.&lt;/p> &lt;p>The bundle size for a Flutter web app is also relatively large: last I checked, release builds using the HTML renderer were ~1.4mb (and a CanvasKit build clocked in at around ~6.5mb!). That's way too much for a static site, where load time is a priority.&lt;/p> &lt;p>I was looking for a Dart-based static site generator to consolidate a few of my sites (including this one). I had been using a mixture of Hugo, PHP, and vanilla HTML/JS, and it was getting to be a pain. I wanted a simple tool that I could point to a directory of &lt;code>.md&lt;/code> files, and generate static HTML with the appropriate routing and styling for upload directly to Cloudflare.&lt;/p> &lt;p>jaspr's static site generation offered the majority of what I needed, and I wrote a small extension to cover the remainder (markdown parsing, basic templating, route configuration). I've &lt;a href="https://github.com/nmfisher/jaspr_blog">open-sourced the repository&lt;/a>, though (as usual) it's not very well documented at this stage.&lt;/p> &lt;p>I've started eating my own dog food, having moved over both this site and the &lt;a href="https://polyvox.app/blog">Polyvox blog&lt;/a>. I've noticed a few painful omissions (code formatting is broken, and there's no way to specify custom tags or otherwise directly embed Javascript or an iframe), which I'll gradually address when I have the time.&lt;/p></content:encoded>
    </item>
    <item>
      <title>3D PBR with Flutter - Talk at Singapore Flutter Meetup</title>
      <link>https://hydroxide.dev/articles/3d-pbr-with-flutter-presentation-to-singapore-flutter-meetup/</link>
      <description>Here are the slides from my talk the Singapore Flutter Meetup on my flutter_filament rendering package.</description>
      <pubDate>Tue, 20 Feb 2024 00:00:00</pubDate>
      <guid isPermaLink="true">https://hydroxide.dev/articles/3d-pbr-with-flutter-presentation-to-singapore-flutter-meetup/</guid>
      <content:encoded>&lt;p>&lt;a href="https://docs.google.com/presentation/d/e/2PACX-1vRgMocYTfvrzP4a-BD8pxxEzuslg60OYAqogecSVIhdQC6cuJfUkJdROtEbje9Gy3GP1MtgOl1_bbZ-/pub?start=false&amp;loop=false&amp;delayms=3000">Here are the slides&lt;/a> from my talk the Singapore Flutter Meetup on my flutter_filament rendering package.&lt;/p></content:encoded>
    </item>
    <item>
      <title>Backpropagation with asymmetric weights</title>
      <link>https://hydroxide.dev/articles/backpropagation-with-asymmetric-weights/</link>
      <description>A number of recent papers have explored learning in deep neural networks without backpropagation, often motivated by the apparent biological implausibility of backpropagation. Supposedly, the</description>
      <pubDate>Sun, 15 Sep 2019 00:00:00</pubDate>
      <guid isPermaLink="true">https://hydroxide.dev/articles/backpropagation-with-asymmetric-weights/</guid>
      <content:encoded>&lt;p>A number of &lt;a href="https://arxiv.org/abs/1909.01311">recent&lt;/a> &lt;a href="https://arxiv.org/abs/1908.01580v1">papers&lt;/a> have explored learning in deep neural networks without backpropagation, often motivated by the apparent &lt;em>biological implausibility&lt;/em> of backpropagation.&lt;/p> &lt;p>Supposedly, the brain's neurons can only transmit electrical impulses in a single direction; neurons have no way of &amp;quot;communicating&amp;quot; error backwards.&lt;/p> &lt;p>So while backpropagation is an effective method for training neural networks, it is not a reasonable analogue for biological learning.&lt;/p> &lt;p>Now I haven't the faintest clue about neuroscience, so I can't comment on whether machine learning &lt;em>does&lt;/em> or &lt;em>should&lt;/em> replicate biological learning.&lt;/p> &lt;p>But I do find &amp;quot;learning without backpropagation&amp;quot; very intriguing for two practical reasons.&lt;/p> &lt;p>First, since the gradients at $layer_{t-1}$ depend on the gradients at $layer_{t}$, backpropagation isn't an inherently parallelizable algorithm. This is a practical bottleneck for efficiently training larger networks.&lt;/p> &lt;p>Second, backpropagation is still computationally expensive. Even where the backwards pass requires only as many operations as the forward pass, this still doubles the effective training cost for a given network.&lt;/p> &lt;p>So is it possible to train a network with conventional gradient-based methods while minimizing the downsides to backpropagation?&lt;/p> &lt;p>According to the &lt;a href="https://arxiv.org/pdf/1411.0247.pdf">theory of feedback alignment&lt;/a>, the answer is yes!&lt;/p> &lt;p>At a high level, feedback alignment means using &lt;em>random weights&lt;/em> to communicate an error signal back through a network, rather than reusing the same weights during the forward and backward passes.&lt;/p> &lt;p>I found this pretty counterintuitive when I first came across the idea. How can you train a network if the error signal is essentially random? Not that I didn't believe the paper, but I really needed to write it out from scratch to really understand what's going on.&lt;/p> &lt;h1>Backpropagation refresher&lt;/h1> &lt;p>Let's revisit the theory behind backpropagation. I'm going to assume some basic familiarity - if you need a refresher, there are dozens of existing tutorials. I recommend &lt;a href="http://neuralnetworksanddeeplearning.com/">Chapter 2 of Michael Nielsen's book&lt;/a>.&lt;/p> &lt;p>Let's take a basic neural network with $t$ layers and a single real-valued output.&lt;/p> &lt;p>The output layer and loss function can be depicted as follows:&lt;/p> &lt;p>&lt;img src="/images/asymmetric-backprop/network_output.png#bg-white" alt="network_output" />&lt;/p> &lt;p>At the end of the forward pass, the network outputs a scalar prediction $\hat{y}$. A loss function $L$ is applied to $\hat{y}$ and $y$, returning a scalar representing the error of the network.&lt;/p> &lt;p>During backpropagation, we first calculate the error of the weights at layer $t-1$ (i.e. $W_{t-1}$) with respect to this loss.&lt;/p> &lt;p>We'll see shortly that this is used to calculate the error at $layer_{t-2}$.&lt;/p> &lt;p>In other words, we propagate the total network loss back through every intermediate layer in the network - hence the term backpropagation.&lt;/p> &lt;p>But what does this mean mathematically?&lt;/p> &lt;p>The network's output is calculated by multiplying $W_{t-1}$ by the activation outputs from the previous layer (zt-2).&lt;/p> &lt;p>$$ \hat{y} = W_{t-1}z_{t-2} $$&lt;/p> &lt;p>For stochastic gradient descent, the update rule for $W_{t-1}$ will be as follows:&lt;/p> &lt;p>$$ W_{t-1} := W_{t-1} - \frac{\partial L}{\partial W_{t-1}} W_{t-1} $$&lt;/p> &lt;p>Per the chain rule:&lt;/p> &lt;p>$$ \frac{\partial L}{\partial W_{t-1}} = \frac{\partial L}{\partial \hat{y}} \frac{\partial \hat{y}}{\partial W_{t-1}} = \frac{\partial L}{\partial \hat{y}} z_{t-2} $$&lt;/p> &lt;p>Since the derivative of $(1)$ with respect to $W_{t-1}$ is $z_{t-2}$.&lt;/p> &lt;p>Now let's look at $z_{t-2}$, the activation outputs from the preceding layer.&lt;/p> &lt;p>$$ z_{t-2} = W_{t-2}z_{t-3} $$&lt;/p> &lt;p>The update rule for $W_{t-2}$ is as follows:&lt;/p> &lt;p>$$ W_{t-2} := W_{t-2} - \frac{\partial L}{\partial W_{t-2}}W_{t-2} $$&lt;/p> &lt;p>And again, by the chain rule:&lt;/p> &lt;p>$$ \frac{\partial L}{\partial W_{t-2}}= \frac{\partial L}{\partial \hat{y}} \frac{\partial \hat{y}}{\partial z_{t-2}}\frac{\partial z_{t-2}}{\partial W_{t-2}}$$&lt;/p> &lt;p>Since&lt;/p> &lt;p>$$ z_{t-2} = \sigma(W_{t-2}z_{t-3}) $$&lt;/p> &lt;p>then&lt;/p> &lt;p>$$ \frac{\partial z_{t-2}}{\partial W_{t-2}} = \sigma\prime(W_{t-2}z_{t-3})z_{t-3} $$&lt;/p> &lt;p>Where $\sigma$ is some non-linear activation function, and $\sigma\prime$ is its corresponding derivative.&lt;/p> &lt;p>This gives us an expression for the last component of $(6)$. But what about the second component - i.e. $\frac{\partial \hat{y}}{\partial z_{t-2}}$?&lt;/p> &lt;p>During the back pass for the last layer, we calculated $\frac{\partial \hat{y}}{\partial W_{t-1}}$ (i.e. the change in network output with respect to the last layer's weights) but not $\frac{\partial \hat{y}}{\partial z_{t-2}}$ (the change with respect to the penultimate activation output).&lt;/p> &lt;p>$$ \frac{\partial \hat{y}}{\partial z_{t-2}} = \frac{\partial L}{\partial \hat{y}} \frac{\partial \hat{y}}{\partial z_{t-2}} $$&lt;/p> &lt;p>$$ = \frac{\partial L}{\partial \hat{y}} W_{t-1}^\intercal $$&lt;/p> &lt;p>Substituting $(8)$ and $(9)$ into $(6)$, we now have:&lt;/p> &lt;p>$$ \frac{\partial L}{\partial W_{t-2}} = \frac{\partial L}{\partial \hat{y}}W_{t-1}^\intercal\sigma'(W_{t-2}z_{t-3})z_{t-3} $$&lt;/p> &lt;p>This line is the key to understanding feedback alignment.&lt;/p> &lt;p>Conventional backpropagation pushes the error &amp;quot;back&amp;quot; through the network via multiplication with that layer's weight matrix.&lt;/p> &lt;p>In other words, the weights used are &lt;em>symmetric&lt;/em> between forward and backward passes.&lt;/p> &lt;p>I've only depicted backpropagation from the last (linear) to the penultimate (non-linear) layer, but the same applies at every layer in a deep network - the transpose of the weight matrix is used at each step to propagate the error backwards.&lt;/p> &lt;p>&amp;quot;Weight symmetry&amp;quot; is therefore just a way of saying &amp;quot;the chain rule requires multiplication by the weight matrix&amp;quot;.&lt;/p> &lt;p>Now what's interesting is considering whether a network can learn &lt;em>without&lt;/em> this weight symmetry. What happens if we replace the transposed weight matrix with purely random values?&lt;/p> &lt;p>Intuitively, you might expect that the network would simply fail to learn. That was certainly my initial reaction.&lt;/p> &lt;p>But you only need to read the title of the feedback alignment paper - &amp;quot;Random feedback weights support learning in deep neural network&amp;quot;. Neural networks can still learn, even when completely random weights are used during the backwards pass!&lt;/p> &lt;h2>Implementing asymmetric weight transfer&lt;/h2> &lt;p>It's difficult to believe, hence why I had a stab at coding an asymmetric network from scratch. This shows that random weights can indeed learn XOR.&lt;/p> &lt;p>&lt;a href="https://github.com/nmfisher/feedback_alignment_demo/blob/master/Library.fs">Here's a link to the full code&lt;/a>, but I'll go through it here line-by-line.&lt;/p> &lt;p>I've used my preferred language (F#), so I'll try and explain some of the F#-specific syntax where I've used.&lt;/p> &lt;p>To make sure I really understood what was going on, I tried to keep the code as close as possible to the maths. This meant avoiding automagic DL frameworks like Keras/PyTorch, but I did use the MathNet library for the underlying matrix operations.&lt;/p> &lt;p>As this was only an exercise, it's all hand-coded, so there's no support for custom loss functions, optimizers, operations or even layers.&lt;/p> &lt;h3>The objective&lt;/h3> &lt;p>Ultimately, we're experimenting to see if a basic neural network can learn an arbitary function with random weights used during the backpass.&lt;/p> &lt;p>XOR seemed a convenient test function to choose - it's not linearly separable, therefore can only be learned via a non-linear model.&lt;/p> &lt;p>Let's start with the main invocation code.&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} let nn = NN(10) let rnd = System.Random() let inputs = [ 0.0,1.0,1.0; 1.0,0.0,1.0; 1.0,1.0,0.0; 0.0,0.0,0.0 ]; let next () = List.item (rnd.Next inputs.Length) inputs {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>This is just basic setup code. We first initialize a neural network object, the XOR inputs/output tuples and a function that returns a random XOR input sample.&lt;/p> &lt;p>Next, the training/evaluation loop:&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} let iterations = 5000 for i in seq { 0..iterations } do let (x1,x2,y1) = next() let x = array2D [ [x1; ]; [x2] ] |&amp;gt; DenseMatrix.ofArray2&lt;br /> {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>We run 5000 training iterations, with a single input/output tuple at each iteration.&lt;/p> &lt;p>The last line is just to ensure the input can be matrix-multiplied with the weights in the first layer. Repeating this on every iteration is very wasteful, but for this experiment/problem, I prefer readability over efficiency.&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} nn.Forward x y1 |&amp;gt; ignore nn.Backward x false nn.Update 0.01 x {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>Next is the crux of the training loop - one forward pass, one backwards pass, and a parameter update. We'll dive into these shortly. The forward pass will return the predicted value, which we will need to explicitly pipe this to the &lt;em>ignore&lt;/em> function (F# complains if a function returns a value that isn't used anywhere).&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} if i % 100 = 0 then let preds = seq { for j in seq { 0..20 } do let (x1,x2,y1) = next() let pred = nn.Forward (array2D [ [x1;];[x2 ] ] |&amp;gt; DenseMatrix.ofArray2) y1 |&amp;gt; (fun x -&amp;gt; match x &amp;gt; 0.5 with | true -&amp;gt; 1 | _ -&amp;gt; 0) if pred = int(y1) then yield true else yield false } let accuracy = (float(preds |&amp;gt; Seq.where id |&amp;gt; Seq.length) / float(preds |&amp;gt; Seq.length)) printfn &amp;quot;Accuracy : %f&amp;quot; accuracy {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>To evaluate the network's performance, every 100 iterations we will feed a random sample into the network and compare the generated prediction with the actual label.&lt;/p> &lt;p>If the network learns the XOR problem perfectly, the accuracy will converge to 1.0 (i.e. 100% of predictions were correct).&lt;/p> &lt;p>Now let's move to the internals of the neural network implementation.&lt;/p> &lt;p>First, define a type that takes a constructor argument for the dimension of each layer.&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} type NN (dim:int) = let mutable w1 = Matrix&lt;double>.Build.Random(dim, 2); // weights let mutable z1 = Matrix&lt;double>.Build.Random(dim, 1); // first layer activation input let mutable w1' = Matrix&lt;double>.Build.Random(dim, 2); // first layer grads let mutable w2 = Matrix&lt;double>.Build.Random(dim, dim); // second layer weights let mutable w2' = Matrix&lt;double>.Build.Random(dim, dim); // second layer grads let mutable z2 = Matrix&lt;double>.Build.Random(dim, 1); // second layer activation input let mutable w3 = Matrix&lt;double>.Build.Random(1, dim); // linear output weights let mutable err = 0.0 {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>Like I said above, I'm rolling everything by hand here with no concern for extensibility.&lt;/p> &lt;p>This means hardcoding three layers - two non-linear layers plus one linear output layer.&lt;/p> &lt;p>To do this, I initialize matrices for each layer's weights, activation outputs and gradients, and the scalar error.&lt;/p> &lt;p>Note that the MathNet Random() function draws from a Gaussian distribution; this is known to be a suboptimal initialization strategy, but that's not important for this particular exercise.&lt;/p> &lt;p>In F#, all variables are immutable by default. These matrices will be updated during every forward/backward pass, so we explicitly denote these matrix variables as mutable.&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} let relu x = max x 0.0 let relu' x = match x &amp;gt; 0.0 with | true -&amp;gt; 1.0 | false -&amp;gt; 0.0 {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>The basic network is only going to use hardcoded ReLU activations, so I define two functions - ReLU and its derivative.&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} member x.Forward (input:Matrix&lt;double>) (output:float) = z1 &amp;lt;- w1 * input |&amp;gt; Matrix.map relu // (dx2) * (2x1) -&amp;gt; (dx1) z2 &amp;lt;- w2 * z1 |&amp;gt; Matrix.map relu let y_hat = (w3 * z2).Row(0).Item(0) if y_hat.Equals(nan) then failwith &amp;quot;NAN&amp;quot; let loss = (abs(y_hat - output)) err &amp;lt;- match y_hat &amp;gt; output with | true -&amp;gt; 1.0 | false -&amp;gt; -1.0 y_hat {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>This is the forward pass for our 3-layer network to learn XOR, accepting a vector of size (1x2) as input and outputting a scalar.&lt;/p> &lt;p>We multiply the first layer's weights by the input vector, followed by the relu activation. Note for simplicity, I haven't included any bias parameter.&lt;/p> &lt;p>This gives us $z_{1}$ (the first layer's activation outputs). Next, a matmul between $z_{1}$ and the second layer's weights, followed by the nonlinearity, giving us $z_{2}$.&lt;/p> &lt;p>Finally, a linear multiplication between $z_{2}$ and the 3rd layer's weights, giving us the network's output.&lt;/p> &lt;p>For the loss function, I am using the mean-squared-error, the derivative of which evaluates to $\hat{y} - y$.&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} member x.Backward (input:Matrix&lt;double>) (symmetric:bool) = if symmetric then w2' &amp;lt;- (err * w3.Transpose()).PointwiseMultiply(Matrix.map relu' z2) w1' &amp;lt;- (w2.Transpose() * w2').PointwiseMultiply((Matrix.map relu' z1)) else&lt;br /> w2' &amp;lt;- (err * r2.Transpose()).PointwiseMultiply(Matrix.map relu' z2) w1' &amp;lt;- (r1 * w2').PointwiseMultiply((Matrix.map relu' z1)) {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>For a conventional (i.e. symmetric) backward pass, the error gradient at the output layer is multiplied by $W_{3}^\intercal$, the transpose of the last layer's weight matrix.&lt;/p> &lt;p>This is then pointwise-multiplied by the derivative of $z_{2}$ (the penultimate layer's activation function), giving the error gradient at the penultimate $layer_{2}$.&lt;/p> &lt;p>Likewise, to propagate the error from $layer_{2}$ to $layer_{1}$, we multiply by $W_{2}^\intercal$.&lt;/p> &lt;p>For now, let's skip the case where asymmetric weights are used.&lt;/p> &lt;p>Once we've finished our backwards pass, we perform the weight update:&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} member x.Update (lr:double) (inputs:Matrix&lt;double>)= w1 &amp;lt;- w1 - (lr * w1' * inputs.Transpose()) w2 &amp;lt;- w2 - (lr * w2' * z1.Transpose()) w3 &amp;lt;- w3 - (lr * err * z2.Transpose()) {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>That completes a single training iteration.&lt;/p> &lt;p>If we run this with symmetric weights, the network will quickly converge to 100% accuracy.&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} Accuracy : 0.476190 Accuracy : 0.619048 Accuracy : 0.761905 Accuracy : 1.000000 {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>Exactly how many iterations this takes will depend on the weight initialization, which is a topic best reserved for another day. But at the least we know our network's implementation is correct!&lt;/p> &lt;p>Let's return to the backwards pass and consider the case of &lt;em>asymmetric&lt;/em> weights.&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} let r2 = Matrix&lt;double>.Build.Random(1, 10) let r1 = Matrix&lt;double>.Build.Random(dim, dim) member x.Backward (input:Matrix&lt;double>) (symmetric:bool) = if symmetric then w2' &amp;lt;- (err * w3.Transpose()).PointwiseMultiply(Matrix.map relu' z2) w1' &amp;lt;- (w2.Transpose() * w2').PointwiseMultiply((Matrix.map relu' z1)) else&lt;br /> w2' &amp;lt;- (err * r2.Transpose()).PointwiseMultiply(Matrix.map relu' z2) w1' &amp;lt;- (r1 * w2').PointwiseMultiply((Matrix.map relu' z1)) {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>Rather than multiplying by the transpose of each layer's weight matrix, what if multiply by &lt;em>completely random weights&lt;/em>. How does that look?&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} Accuracy : 0.857143 Accuracy : 0.666667 Accuracy : 0.761905 Accuracy : 0.857143 Accuracy : 1.000000 Accuracy : 1.000000 Accuracy : 1.000000 {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>The network still reaches 100% accuracy, showing that weight symmetry isn't necessary for the network to learn a non-linear function.&lt;/p> &lt;p>It's important to note that the random backpass weights are fixed - they are not adjusted or learned during the parameter update stage.&lt;/p> &lt;p>As a side-note, another curious result is that re-randomizing the weights during each backwards pass also doesn't prevent the network from learning.&lt;/p> &lt;p>Now why is this effective?&lt;/p> &lt;p>My own hand-wavy explanation is that training with random backpass weights equates to training a pseudo-objective $W_{r}$ rather than $W_{z}$. If this pseudo-objective is optimized with respect to the &amp;quot;true&amp;quot; loss, the direction of the gradients will be pushed in the direction of the &amp;quot;true&amp;quot; gradient. This means the pseudo-objective will converge towards the true objective.&lt;/p> &lt;p>I also speculate that there's a close relationship between feedback alignment and random projections.&lt;/p> &lt;p>Either way, the paper sets out a much more formal/rigorous explanation and proof.&lt;/p> &lt;p>I found it very instructive to go through this line-by-line, so I hope that others find this useful too. If you have any comments, criticisms or errata, please reach out in the comments or via Twitter!&lt;/p></content:encoded>
    </item>
    <item>
      <title>Calling F#/.NET code from Flutter with Mono</title>
      <link>https://hydroxide.dev/articles/calling-fsharp-dotnet-code-from-flutter/</link>
      <description>I'm obliged to issue a severe warning to anyone who found their way here. DO NOT DO anything I explain below. Seriously. I'd sooner recommend juggling a pair of flaming chainsaws. Everything I</description>
      <pubDate>Mon, 02 Sep 2019 04:58:40</pubDate>
      <guid isPermaLink="true">https://hydroxide.dev/articles/calling-fsharp-dotnet-code-from-flutter/</guid>
      <content:encoded>&lt;p>I'm obliged to issue a severe warning to anyone who found their way here.&lt;/p> &lt;p>&lt;em>DO NOT DO&lt;/em> anything I explain below.&lt;/p> &lt;p>Seriously.&lt;/p> &lt;p>I'd sooner recommend juggling a pair of flaming chainsaws. Everything I'm about to say is a terrible idea and you should ignore it completely.&lt;/p> &lt;p>Why?&lt;/p> &lt;p>Because although it's &lt;em>possible&lt;/em> to glue the Mono and Flutter runtimes together, it will leave your application looking like the software equivalent of the Somme circa 1916.&lt;/p> &lt;p>Only try this yourself if you really, really want an application that:&lt;/p> &lt;ul> &lt;li>not testable end-to-end&lt;/li> &lt;li>double the binary size it was originally&lt;/li> &lt;li>dependent on C, CMake, F# and MSBuild, most of which are foreign (or at least &lt;em>new&lt;/em>) to your average mobile developer&lt;/li> &lt;li>only buildable in parts, since no single build system can address the assembly dependency/versioning issues you'll experience on the .NET side with the conventional Android/Objective-C/Swift build system.&lt;/li> &lt;/ul> &lt;p>If you really need .NET in a mobile application, use Xamarin.&lt;/p> &lt;p>If you really want to use Flutter, rewrite your library in Dart.&lt;/p> &lt;p>I beg you - don't mix and match.&lt;/p> &lt;p>I did it, and ended up with such a mess that I went back and took the second option. In less time than it took me to do the below, I might add.&lt;/p> &lt;p>I honestly can't think of a &lt;em>single&lt;/em> situation where the trade-off will be net positive.&lt;/p> &lt;p>With that out of the way, let's look at the motivation.&lt;/p> &lt;h2>Bridging Flutter and .NET with Mono&lt;/h2> &lt;p>Say you have an existing cross-platform mobile application written in Flutter/Dart.&lt;/p> &lt;p>One day you happen upon a marvellous .NET library written in (say) F# that takes your application from humdrum to unicorn.&lt;/p> &lt;p>You're itching to integrate the two. How do you do it?&lt;/p> &lt;p>First, a quick recap of what a Flutter application looks like.&lt;/p> &lt;p>&lt;img src="/images/dotnet-flutter/flutter-application-architecture.png" alt="architecture-overview" />&lt;/p> &lt;p>A single Dart code base with a nice layout/rendering framework that runs on both Android and iOS via the Dart VM.&lt;/p> &lt;p>On Android, your application code and the framework itself are compiled to bytecode, which is then JIT-compiled by the Dart VM and executed by the operating system on the CPU.&lt;/p> &lt;p>Apple does not allow JIT-compiled code on iOS, so the code is AOT-compiled and skips the bytecode-JIT compile step.&lt;/p> &lt;p>Bear in mind I'm using a fairly loose definition of &amp;quot;operating system&amp;quot; here, encompassing emulators, simulators, system libraries and native APIs.&lt;/p> &lt;p>Now on the .NET side, things look reasonably similar. F# code, compiled to CIL (bytecode), JIT-compiled via the runtime (.NET or Mono) and executed by the OS on the CPU.&lt;/p> &lt;p>&lt;img src="/images/dotnet-flutter/dotnet-application-architecture.png" alt="architecture-overview" />&lt;/p> &lt;p>For a Flutter application to interop with native code, the two runtimes (or static libraries, in an AOT context) need to communicate.&lt;/p> &lt;p>Although this is under development, Dart does &lt;a href="https://dart.dev/server/c-interop">not currently support native interop&lt;/a>. Flutter can only communicate with native code via platform channels in Java (for Android) or Objective C/Swift. These are straightforward and well-documented, so I won't go into those here.&lt;/p> &lt;p>To interop with a F#/.NET assembly, we'll need to embed a native CLR capable of running on arm64, armv7 or x86 (for emulators), as well as the iOS and Android operating systems.&lt;/p> &lt;p>Mono is the only runtime that fits the bill here. Neither .NET Framework nor .NET Core are available for mobile architectures - I assume this is because Microsoft acquired Xamarin (which runs on Mono under the hood), and supporting two cross-platform CLR implementations wouldn't be prudent.&lt;/p> &lt;h1>Embedding Mono&lt;/h1> &lt;p>So, how do we &amp;quot;embed&amp;quot; Mono? What does that even mean?&lt;/p> &lt;p>Similar to .NET Framework, a Mono &amp;quot;installation&amp;quot; is a set of libraries (either statically or dynamically compiled) containing basic CLR types (String, Object, etc), system libraries (file, memory allocation, etc), a JIT compiler and instructions (&amp;quot;trampolines&amp;quot;) needed to invoke JIT-compiled code. Methods in your own .NET assembly are invoked via the Mono runtime.&lt;/p> &lt;p>&amp;quot;Embedding&amp;quot; Mono basically means:&lt;/p> &lt;ul> &lt;li>Building/deploying the correct Mono libraries for the given OS/architecture combination&lt;/li> &lt;li>Initializing the Mono runtime to ensure all base libraries are correctly loaded&lt;/li> &lt;li>Glue code to change data going in/coming out of Mono from &amp;quot;native&amp;quot; data types to &amp;quot;Mono&amp;quot; types (e.g. converting JNI JStrings or Objective-C NSString to Mono Strings)&lt;/li> &lt;li>Glue code to locate your own assembly (and the methods/classes you want to use) so you can pass your data in and handle any exceptions.&lt;/li> &lt;/ul> &lt;p>Note this all occurs in native (C) code - so on Android, for example, your application will end up looking like this:&lt;/p> &lt;p>&lt;img src="/images/dotnet-flutter/dart-java-c-dotnet.png" alt="architecture-overview" />&lt;/p> &lt;p>By now, you might be starting to understand why the complexity just isn't worth it.&lt;/p> &lt;h1>Compiling Mono&lt;/h1> &lt;p>To begin with, you'll need to compile the Mono runtime and a cross-compiler (since all assemblies need to be AOT-compiled for iOS).&lt;/p> &lt;p>Clone the repository at &lt;a href="https://github.com/mono/mono/">https://github.com/mono/mono/&lt;/a> and follow the instructions in the sdks directory (note this requires an existing Mono installation to bootstrap, as well as automake and ninjna).&lt;/p> &lt;p>For Android, it's theoretically possible to do this on Windows, but I had major issues via both cygwin and WSL. In the end, I built all the Android-specific libraries on Linux. For the iOS libraries, everything needs to be built on MacOS.&lt;/p> &lt;p>Under sdks/out, you'll end up with Mono builds for each architecture/OS combination:&lt;/p> &lt;ul> &lt;li>./android-arm64-v8a-release&lt;/li> &lt;li>./android-armeabi-v7a-release&lt;/li> &lt;li>./ios-bcl&lt;/li> &lt;li>./ios-cross64-release&lt;/li> &lt;li>./ios-target64-release&lt;/li> &lt;/ul> &lt;p>This &lt;em>should&lt;/em> build the base class libraries (BCLs - standard CLI libraries handling core tasks like assembly loading, security, etc). However, if it does not, you may need to run autogen and make in the root directory first:&lt;/p> &lt;p>{{&amp;lt; highlight shell &amp;gt;}} ./autogen.sh --with-monodroid # for Android ./autogen.sh --with-monotouch # for iOS make {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;h1>Compile your own assembly&lt;/h1> &lt;p>Let's say the F# module you want to invoke looks like this:&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} module Hello&lt;/p> &lt;p>[&lt;EntryPoint>] let say x = sprintf &amp;quot;You said %s&amp;quot; x {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>Compile this with fsharpc (or dotnet build), targeting your main project as a .NET 4.6.2+ console application.&lt;/p> &lt;p>Mono requires an assembly entry point to setup the correct AppDomain paths, and there's some incompatibility with .NET Core apps. Note the EntryPoint annotation.&lt;/p> &lt;h1>[Android] Copy libraries&lt;/h1> &lt;p>On Android, zip all project DLLs, BCLs and config together.&lt;/p> &lt;p>{{&amp;lt; highlight shell &amp;gt;}} mkdir myproj mkdir myproj/lib mkdir myproj/lib/mono mkdir myproj/lib/mono/4.5 mkdir myproj/etc mkdir myproj/etc/mono cp $SRCDIR/hello.dll myproj cp -R $MONO_REPO_DIR/mcs/class/lib/monotouch/&lt;em>.dll myproj/lib # replace monotouch with monodroid for Android cp -R $MONO_REPO_DIR/mcs/class/lib/monotouch/Facades/&lt;/em>.dll myproj/lib mv myproj/lib/mscorlib.dll myproj/lib/mono/4.5 cp $MONO_REPO_DIR/sdks/builds/ios-target64-release/data/config myproj/etc/mono # replace ios-target-64 with android-arm64-v8a-release for Android {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>I've used the environment variables &lt;em>SRCDIR&lt;/em> and &lt;em>MONO_REPO_DIR&lt;/em> arbitrarily - you will need to set these to the correct directories.&lt;/p> &lt;p>The config file is needed as Mono maps certain DLLs to specific native libraries, depending on the chosen platform.&lt;/p> &lt;p>On my build, these config files are the same between architectures (e.g. armv8 vs armv7), so it doesn't matter whether you choose ios-target-64/ios-target-32/etc. However, this may change in future, so take care.&lt;/p> &lt;p>There's nothing particularly important about this folder structure. As we'll see shortly, Mono allows us to manually set the config and base library directories manually.&lt;/p> &lt;p>However, two points to note:&lt;/p> &lt;ul> &lt;li>Mono expects to find mscorlib.dll under the mono/4.5 subfolder&lt;/li> &lt;li>Mono looks for native remapped libraries in the same directory as its DLLs, meaning we will need to copy our architecture-specific libraries to the myproj/lib directory inside our application.&lt;/li> &lt;/ul> &lt;p>In your Flutter pubspec.yaml, add this zip file as an asset. You'll need to write your own script at startup to unzip these libraries to the application's ${dataDir}/app_flutter subdirectory. &lt;a href="https://gist.github.com/nmfisher/467f7a3d441147b9c3829988b151ca0d">I've created a gist here&lt;/a> that will help.&lt;/p> &lt;p>Ideally, I'd integrate this with Gradle build, but I couldn't figure out a way to embed files other than dynamic libraries (.so).&lt;/p> &lt;p>I assume Android requires all non-library files to be packaged as application assets.&lt;/p> &lt;h1>[iOS] AOT compile libraries and link via Xcode&lt;/h1> &lt;p>On iOS, all assemblies need to be AOT-compiled and copied via Xcode (rather than zipped and extracted at runtime).&lt;/p> &lt;p>I encountered a &lt;em>lot&lt;/em> of difficulty in getting this all to work.&lt;/p> &lt;p>A successful compile doesn't mean the libraries will actually run.&lt;/p> &lt;p>There are a lot of strong, hidden dependencies under the hood that mean you need to use the correct version of mscorlib.dll, FSharp.Core.dll, compile switches, and so on.&lt;/p> &lt;p>Roughly speaking, I needed to:&lt;/p> &lt;ul> &lt;li> &lt;p>use the FSharp.Core.dll from the Xamarin.iOS binaries repository. Don't copy from NuGet or your project build folder - I believe an older build is required under Mono.&lt;/p> &lt;/li> &lt;li> &lt;p>use exactly the same BCLs from your Mono build, not the assemblies used to build your project&lt;/p> &lt;/li> &lt;li> &lt;p>compile with --static and -direct-icalls for mscorlib.&lt;/p> &lt;/li> &lt;/ul> &lt;p>This &lt;em>shouldn't&lt;/em> be necessary if you link against the icall library, but Mono crashed every time without it, complaining that the icall lookup table couldn't be found.&lt;/p> &lt;ul> &lt;li>create your own ld/assembler scripts for each architecture.&lt;/li> &lt;/ul> &lt;p>Again, this shouldn't be needed, but my Mono installation wasn't calling the native assembler/linker correctly, so I had to set this up manually.&lt;/p> &lt;p>When you compile, use the correct tool-prefix switch (i.e. tool-prefix=aarch64-, or tool-prefix=armv7s-)&lt;/p> &lt;p>{{&amp;lt; highlight shell &amp;gt;}} $ cat /usr/local/bin/aarch64-as as -arch arm64 $@&lt;/p> &lt;p>$ cat /usr/local/bin/aarch64-ld clang -Xlinker -v -Xlinker -F/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/ -arch arm64 $@&lt;/p> &lt;p>$ cat /usr/local/bin/armv7s-as as -arch armv7 $@&lt;/p> &lt;p>$ cat /usr/local/bin/armv7s-ld clang -Xlinker -v -Xlinker -F/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/ -arch armv7s $@ {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>{{&amp;lt; highlight shell &amp;gt;}} mono --aot=full,static,tool-prefix=aarch64,direct-icalls mscorlib.dll {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>Rinse and repeat for all BCLs and project assemblies (excluding the static and direct-icalls switches for DLLs other than mscorlib.dll).&lt;/p> &lt;h1>[Android] Link libraries via CMake&lt;/h1> &lt;p>I won't cover the ins-and-outs of including CMake in your build pipeline via the Gradle/Flutter build process. Long story short, you'll need to copy libmonosgen-2.0.so for each architecture to the corresponding folders in your Flutter project's android/src/main/jniLibs directory:&lt;/p> &lt;p>{{&amp;lt; highlight shell &amp;gt;}} cp $MONO_REPO_DIR/sdks/out/android-arm64-v8a-release/lib/libmonosgen-2.0.so $FLUTTER_PROJECT_DIR/android/src/main/jniLibs/arm64-v8a cp $MONO_REPO_DIR/sdks/out/android-armeabi-v7a-release/lib/libmonosgen-2.0.so $FLUTTER_PROJECT_DIR/android/src/main/jniLibs/armeabi-v7a&lt;/p> &lt;h1>repeat for all architectures&lt;/h1> &lt;p>{{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>Your CMakeLists.txt file will need to include the following:&lt;/p> &lt;p>{{&amp;lt; highlight shell &amp;gt;}} link_directories(&amp;quot;${Project_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}&amp;quot;) add_library(hello SHARED hello.c) target_include_directories (parser PUBLIC &amp;quot;$MONO_REPO_DIR/sdks/out/android-x86-release/include/mono-2.0&amp;quot; &amp;quot;$GLIB_SRC_DIR&amp;quot; &amp;quot;$GLIB_SRC_DIR/glib&amp;quot; &amp;quot;$GLIB_BUILD_DIR/glib&amp;quot; ) target_link_libraries(hello monosgen-2.0 android log gcc m) {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;h1>[iOS] Link libraries via Xcode&lt;/h1> &lt;p>In Xcode, you'll need to link your entire application with the Mono runtime &lt;em>and&lt;/em> the libraries you just compiled (static or otherwise). You'll also need to copy the original assemblies (even though the actual compiled code is statically linked, the runtime still needs the reference metadata from the assemblies to load this code).&lt;/p> &lt;p>Under your Runner - Build Phases - &amp;quot;Link Binary with Libraries&amp;quot;, link in all AOT-compiled libraries. For dynamic libraries, these also need to be included under &amp;quot;Embed Frameworks&amp;quot;.&lt;/p> &lt;p>For dynamic libraries, you will also need to use install-name-tool to change the ID of each dynamic library and its rpath.&lt;/p> &lt;p>You'll also need to include both glib and Mono header files.&lt;/p> &lt;p>Do &lt;em>not&lt;/em> try and set these directly via the Pods project in XCode - these settings will be overwritten. Set these via the ios/yourapp.podspec file as follows:&lt;/p> &lt;p>{{&amp;lt; highlight shell &amp;gt;}} s.pod_target_xcconfig = { 'USER_HEADER_SEARCH_PATHS' =&amp;gt; '/usr/local/lib/glib-2.0/include /usr/local/include/glib-2.0 $MONO_REPO_DIR/include/mono-2.0',&lt;br /> 'ALWAYS_SEARCH_USER_PATHS' =&amp;gt; 'YES' } {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>The standard Mono SDK build doesn't include x86-64 binaries, so if you want to run on the simulator, you'll need to manually lipo all the libmonosgen-2.0-compat.dylib files into one &amp;quot;fat&amp;quot; library and reference that instead:&lt;/p> &lt;p>{{&amp;lt; highlight shell &amp;gt;}} lipo $MONO_REPO_DIR/sdks/out/target-ios-target-64/libmonosgen-2.0.compat.dylib $MONO_REPO_DIR/sdks/out/target-ios-target-32/libmonosgen-2.0.dylib &lt;strong>repeat for all architectures&lt;/strong> -create -output libmonosgen-2.0_fat.dylib {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;h1>C code to load the Mono runtime and invoke assembly method&lt;/h1> &lt;p>This step is quite involved, and becomes very app-specific once the runtime is loaded.&lt;/p> &lt;p>Refer to the &lt;a href="https://www.mono-project.com/docs/advanced/embedding/">Mono documentation&lt;/a> for further information on converting native types to pass to the Mono runtimes, finding methods/classes, and so on.&lt;/p> &lt;p>I'll just cover the runtime initialization here.&lt;/p> &lt;p>Note your interop code will need to build against glib: {{&amp;lt; highlight shell &amp;gt;}} yum install glib # Android brew install glib # ios {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>One concern I have is that glibconfig.h is a generated/architecture-specific file containing typedefs for certain sizes. Although I didn't experience any issues from this, I can't guarantee that this is OK. Someone with more experience would need to comment.&lt;/p> &lt;p>Roughly speaking, the native code will:&lt;/p> &lt;ul> &lt;li>Find the absolute path of the app install directory&lt;/li> &lt;li>Find the subdirectory where XCode copied the dynamic libraries and DLLs&lt;/li> &lt;li>Call mono_set_dirs on this path&lt;/li> &lt;li>[iOS] Call mono_aot_register_module on any statically compiled DLLs&lt;/li> &lt;li>[iOS] Call mono_jit_set_aot_mode(MONO_AOT_MODE_FULL);&lt;/li> &lt;li>Call mono_jit_init(&amp;quot;myapp&amp;quot;);&lt;/li> &lt;li>Call mono_domain_assembly_open (myDomain, path_to_your_dll);&lt;/li> &lt;/ul> &lt;p>Here's a direct copy and paste for the iOS portion of my code (note I actually bundled all Mono libraries/DLLs as a separate framework, so yours may look slightly different):&lt;/p> &lt;p>{{&amp;lt; highlight C &amp;gt;}} char* subdir = &amp;quot;/Frameworks/ParserAOT.framework/lib/&amp;quot;; assembly_path = malloc(strlen(path) + strlen(subdir)); strcpy(assembly_path, path); strcat(assembly_path, subdir); parser_dll_path = malloc(strlen(assembly_path) + strlen(ASSEMBLY_FILE_NAME)); strcpy(parser_dll_path, assembly_path); strcat(parser_dll_path, ASSEMBLY_FILE_NAME);&lt;/p> &lt;p>config_path = malloc(strlen(assembly_path) + strlen(&amp;quot;config&amp;quot;)); strcat(config_path, assembly_path); strcat(config_path, &amp;quot;config&amp;quot;);&lt;/p> &lt;p>mono_set_dirs(assembly_path, assembly_path); mono_aot_register_module(mono_aot_module_mscorlib_info); mono_config_parse (config_path); mono_jit_set_aot_mode(MONO_AOT_MODE_FULL); myDomain = mono_jit_init(&amp;quot;myapp&amp;quot;); MonoAssembly *assembly = mono_domain_assembly_open (myDomain, parser_dll_path); {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;h1>Bridge Dart/Java/Objective C code to native&lt;/h1> &lt;p>Your Dart code will need to send a message to your Java/Objective C code via platform channel. This is pretty straightforward so I won't cover it here.&lt;/p> &lt;p>On the Android/Java side, you'll then need to bounce this method to native code via JNI method. This involves adding a method signature in your Java class like:&lt;/p> &lt;p>{{&amp;lt; highlight Java &amp;gt;}} public native String invokeJNI(String method, String data); {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>...and a matching C method like:&lt;/p> &lt;p>{{&amp;lt; highlight Java &amp;gt;}} JNIEXPORT jobjectArray JNICALL Java_com_avinium_parser_ParserPlugin_invokeJNI(JNIEnv *env, jobject obj, jstring method, jstring json) { {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>Again, read up on JNI for a proper implementation.&lt;/p> &lt;p>For iOS apps, invoke your C method directly from your Objective C code.&lt;/p> &lt;h1>Postscript&lt;/h1> &lt;p>There you go. A very high-level view of what's needed to get a Flutter application to communicate with a F# (or any .NET) assembly.&lt;/p> &lt;p>Worth it? Definitely not.&lt;/p> &lt;p>This was an insane amount of work for zero eventual benefit.&lt;/p> &lt;p>If I'm totally honest, I should have given up halfway through.&lt;/p> &lt;p>I only persisted because the OCD took over and I was compelled to finish what I started, no matter how daft.&lt;/p> &lt;p>As a side note - you may think that Mono's mkbundle will package everything together as a single shared library - the Mono runtime, BCLs, your application DLL and its dependencies.&lt;/p> &lt;p>This will actually work - but for x86_64 only. mkbundle is intended for desktop binaries and will not handle mobile.&lt;/p></content:encoded>
    </item>
    <item>
      <title>Nelder Mead Optimization with F# + Fable</title>
      <link>https://hydroxide.dev/articles/nelder-mead-optimization-with-fsharp-and-fable/</link>
      <description>Gradient descent is a spectacularly effective optimization technique, but it's not the only method for optimizing non-convex functions. There are a number of alternative numerical methods that can be</description>
      <pubDate>Tue, 02 Jul 2019 04:58:40</pubDate>
      <guid isPermaLink="true">https://hydroxide.dev/articles/nelder-mead-optimization-with-fsharp-and-fable/</guid>
      <content:encoded>&lt;p>Gradient descent is a spectacularly effective optimization technique, but it's not the only method for optimizing non-convex functions. There are a number of alternative numerical methods that can be used to solve functions without using or calculating the gradient of that function (or indeed, where the gradient of such function isn't known).&lt;/p> &lt;p>The Nelder-Mead algorithm is one such numerical method that was &lt;a href="https://en.wikipedia.org/wiki/Nelder%E2%80%93Mead_method">first proposed in 1965&lt;/a>. As we'll see, the method is actually quite straightforward, evaluating the function at various points within a neighbourhood, then iteratively moving those points in the optimal direction until convergence.&lt;/p> &lt;p>Assume we have some task T, like constraint solving, image classification. For current purposes, it doesn't really matter &lt;em>what&lt;/em> this task is. We just know that our task takes some input $x$, and we want to solve it by producing some numerical output.&lt;/p> &lt;p>To do this, we formulate the task as some function $f$ of $x$ and parameters $\theta$.&lt;/p> &lt;p>With this formulation, the minimum of the function (i.e. $\min f(x;\theta)$) is the optimal solution for T.&lt;/p> &lt;p>Let's leave aside how we decided this formulation (or the parameterization of that function). That's obviously a critical step in the optimization process, just not the focus of this post.&lt;/p> &lt;p>So we want to minimize $f(x;\theta)$ to find the best parameters for our task T.&lt;/p> &lt;p>The logic behind NM is quite straightforward - evaluate the objective function at a few random points, try and move the worst point towards the middle of the first two, and repeat until the points converge to the minimum.&lt;/p> &lt;p>Let's look at a function where we know the minimum, like $f(x) = x^2$. We'll work in two dimensions to make things easier to visualize.&lt;/p> &lt;p>&lt;img src="/images/nelder-mead/function.png" alt="function" />&lt;/p> &lt;p>NM starts a set of n+1 random points (where n is the dimensionality of the domain of $f$). This is known as a simplex, and in two dimensions, forms a triangle. Our input here is only one-dimensional, so the simplex is simply a line-segment - but I chose three points anyway for the purpose of illustration.&lt;/p> &lt;p>&lt;img src="/images/nelder-mead/simplex.png" alt="simplex" />&lt;/p> &lt;p>These points are then sorted according to their performance under their objective function - leaving us with points B, G and W (think &amp;quot;best&amp;quot;, &amp;quot;good&amp;quot;, and &amp;quot;worst&amp;quot;).&lt;/p> &lt;p>&lt;img src="/images/nelder-mead/wgb.png" alt="wgb" />&lt;/p> &lt;p>NM then finds the midpoint M between B and G, and performs a sequence of different transformations to M, evaluating the objective function at M and comparing it with the function evaluated at B or G.&lt;/p> &lt;p>&lt;img src="/images/nelder-mead/midpoint.png" alt="midpoint" />&lt;/p> &lt;p>For example, M is first reflected towards W - if this reflection R performs better than B (i.e. the objective function evaluated at R is closer to zero than evaluated at B), an extension is performed in the same direction.&lt;/p> &lt;p>&lt;img src="/images/nelder-mead/reflection.png" alt="screenshot1" />&lt;/p> &lt;p>Depending on which is better, point W is replaced with point R. Alternatively, if the reflection performs worse, the midpoint is contracted or shrunk in the same direction as W.&lt;/p> &lt;p>&lt;img src="/images/nelder-mead/extension.png" alt="extension" />&lt;/p> &lt;p>&lt;img src="/images/nelder-mead/contraction.png" alt="contraction" />&lt;/p> &lt;p>This process is repeated until the distance between the original and transformed points falls beneath some threshold.&lt;/p> &lt;p>Here is an F# implementation for Nelder Mead:&lt;/p> &lt;p>{{&amp;lt; highlight fsharp &amp;gt;}} type Simplex(points:Point[], objective:Point -&amp;gt; float) =&lt;/p> &lt;pre>&lt;code> let scored = points |&amp;gt; Array.map( fun x -&amp;gt; x, objective x) |&amp;gt; Array.sortBy (fun (x,y) -&amp;gt; y) let best = fst scored.[0] let f_best = snd scored.[0] let good = fst scored.[1] let f_good = snd scored.[1] let worst = fst scored.[2] let f_worst = snd scored.[2] let midpoint = (best + good) / 2.0 member this.compute () = let reflect = (midpoint * 2.0) - worst let f_reflect = objective reflect match f_reflect &amp;lt; f_good with | true -&amp;gt; match f_best &amp;lt; f_reflect with | true -&amp;gt; Simplex([|best; good; reflect|], objective) | false -&amp;gt; let extension = (reflect * 2) - midpoint let f_extension = objective extension match f_extension &amp;lt; f_reflect with | true -&amp;gt; Simplex([|best; good; extension|], objective) | false -&amp;gt; Simplex([|best; good; reflect|], objective) | false -&amp;gt; match f_reflect &amp;lt; f_worst with | true -&amp;gt; Simplex([|best; good; reflect|], objective) | false -&amp;gt; let c1 = (midpoint + worst) / 2 let c2 = (midpoint + reflect) / 2 let contraction = match objective c1 &amp;gt; objective c2 with | true -&amp;gt; c2 | false -&amp;gt; c1 let f_contraction = objective contraction match f_contraction &amp;lt; f_worst with | true -&amp;gt; Simplex([|best; good; contraction|], objective) | false -&amp;gt; let shrinkage = (best + worst) / 2 let f_shrinkage = objective shrinkage Simplex([|best; midpoint; shrinkage|], objective) &lt;/code>&lt;/pre> &lt;p>{{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>This is a fairly bare-bones implementation (the canonical version should contain scale parameters for each transformation and checks at each iteration to ensure the simplex has actually shrunk).&lt;/p> &lt;p>This was also a good opportunity to take &lt;a href="https://fable.io">Fable&lt;/a> for a spin. Fable is a transpiler, letting you write F# code that will run/render in the browser. Here's Nelder-Mead in action, running courtesy of Fable.&lt;/p> &lt;div id="graph" style="height:500px;margin-bottom:100px">&lt;/div> &lt;p>Click &amp;quot;Reset&amp;quot; to reset the simplex to three randomly chosen points, then click &amp;quot;Step&amp;quot; to go through one iteration of Nelder-Mead. You'll see the simplex converging to 0 - the optimal solution for our basic function.&lt;/p> &lt;p>One of the cool things about this is that the above uses Plotly.js for charting library, &lt;em>all invoked from F# code&lt;/em>. ts2fable is a Typescript to F# transpiler bundled with , letting you generate F# bindings for any Typescript library. This allowed me to take the Plotly.js definitions from the &lt;a href="https://github.com/DefinitelyTyped/DefinitelyTyped">DefinitelyTyped project&lt;/a>, generate F# bindings and stitch the Nelder-Mead algorithm/buttons to the chart, all via F#. Nifty, right?&lt;/p> &lt;p>Admittedly, the project is still relatively young so it's not all smooth sailing (and ts2fable in particular generates some odd code that needed manual fixing).&lt;/p> &lt;p>But seeing the F# language venture beyond the .NET runtime is very promising. I initially tried a Javascript implementation of Nelder-Mead. Not only was it literally twice as long as the F# version, and only half as comprehensible. I find ML-style pattern matching an excellent idiom for representing algorithmic work concisely and with minimal clutter.&lt;/p> &lt;p>&lt;a href="https://github.com/nmfisher/nelder-mead-fsharp-fable">Code is available on GitHub&lt;/a> if you want to check it out.&lt;/p> &lt;p>Header image courtesy of &lt;a href="https://www.flickr.com/photos/baccharus/4477128396/in/photolist-7PCshU-goXXdY-arRd8Q-nTVxWZ-nRQk4V-nzEcs4-nzCZrV-nS8xY2-nRZM79-nzCAvz-nRQRWr-nRZhfS-iFvNCB-pi6EBF-27L1Dx-9wvqkZ-nzCzEr-8N1MRa-nQ68ZN-8cGEYg-6Zugh1-3atRxK-fzgnPA-ouGuNq-73ichN-pSaPY6-aFYGX8-5njKyG-mzSk9C-8NxWST-AYZKkB-aE3L1Z-5FQdPa-2m9x5e-6BtiJn-93hKNt-dhweee-qRcBeK-p48tfc-a8QbUY-5MCqoj-o5MgFL-ndzkFj-grWwLh-95dssq-kouCLj-d74rru-pnC9Jn-9nWYNE-eHGVcN">Flickr&lt;/a>&lt;/p> &lt;script src="https://cdn.plot.ly/plotly-latest.min.js">&lt;/script> &lt;script src="/js/nelder-mead/bundle.js">&lt;/script></content:encoded>
    </item>
    <item>
      <title>WPF ItemsSource not updating when items added to ObservableCollection?</title>
      <link>https://hydroxide.dev/articles/wpf-itemssource-not-updating-when-items-added-to-observablecollection/</link>
      <description>Let's say our WPF application has an ItemsControl whose ItemsSource is bound to an ObservableCollection. {{&amp;lt; highlight python &amp;gt;}} &amp;lt;UserControl.Resources&amp;gt; &amp;lt;/UserControl.Resources&amp;gt;</description>
      <pubDate>Sat, 01 Sep 2018 04:58:40</pubDate>
      <guid isPermaLink="true">https://hydroxide.dev/articles/wpf-itemssource-not-updating-when-items-added-to-observablecollection/</guid>
      <content:encoded>&lt;p>Let's say our WPF application has an ItemsControl whose ItemsSource is bound to an ObservableCollection. {{&amp;lt; highlight python &amp;gt;}} &lt;UserControl x:Class="Lexico.View.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Lexico.View" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> &amp;lt;UserControl.Resources&amp;gt; &lt;ResourceDictionary Source="pack://application:,,,/Lexico.WPF;component/Resources/MasterDictionary.xaml" /> &amp;lt;/UserControl.Resources&amp;gt; &lt;Grid> &lt;ItemsControl ItemsSource="{Binding Items}" Visibility="{Binding Items, Converter={StaticResource NonEmptyContainerVisCollapseConverter}}"> &lt;/ItemsControl> &lt;TextBlock Visibility="{Binding Items, Converter={StaticResource EmptyContainerVisibilityCollapseConverter}}"> Looks like this document doesn't contain any questionnaire items. &lt;/TextBlock> &lt;/Grid> &lt;/UserControl> {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>The Visibility property of the ItemsControl is bound to a custom IValueConverter that returns Visibility.Visible if the ItemsSource is non-empty, or Visibility.Collapsed if empty. Additionally, we have a TextBox with a reverse IValueConverter&lt;/p> &lt;p>In other words, if our ObservableCollection is empty, we display the TextBox to notify the user there's nothing there, otherwise we show the ItemsControl.&lt;/p> &lt;p>If everything goes to plan, on first load, on first load, we should see our &amp;quot;empty&amp;quot; message.&lt;/p> &lt;p>&lt;img src="/assets/img/wpf-itemssource-not-updating-when-items-added-to-observablecollection-1.png" alt="screenshot1" />&lt;/p> &lt;p>So far so good.&lt;/p> &lt;p>Now if we add an item to the ObservableCollection, the ItemsControl should update and we should see our newly added object.&lt;/p> &lt;p>&lt;img src="/assets/img/wpf-itemssource-not-updating-when-items-added-to-observablecollection-2.png" alt="screenshot1" />&lt;/p> &lt;p>No bueno - the UI isn't updating; seemingly nothing has been added.&lt;/p> &lt;p>If we step through the debugger, though, we can verify that everything is wired up correctly. The item is definitely added to the collection.&lt;/p> &lt;p>&lt;img src="/assets/img/wpf-itemssource-not-updating-when-items-added-to-observablecollection-3.png" alt="screenshot1" />&lt;/p> &lt;p>So what's going on?&lt;/p> &lt;p>The key here is the Visibility binding:&lt;/p> &lt;p>{{&amp;lt; highlight python &amp;gt;}} &lt;ItemsControl ItemsSource="{Binding Items}" Visibility="{Binding Items, Converter={StaticResource NonEmptyContainerVisCollapseConverter}}"> &lt;/ItemsControl> &lt;TextBlock Visibility="{Binding Items, Converter={StaticResource EmptyContainerVisibilityCollapseConverter}}"> Looks like this document doesn't contain any questionnaire items. &lt;/TextBlock> {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>If we remove this, suddenly everything clicks!&lt;/p> &lt;p>&lt;img src="/assets/img/wpf-itemssource-not-updating-when-items-added-to-observablecollection-4.png" alt="screenshot1" />&lt;/p> &lt;p>The logic is pretty straightforward - feel free to kick yourself like I did when I figured it out. ObservableCollection raises a CollectionChanged event whenever an item is added or removed - the ItemsSource binding will listen for this event and refresh appropriately.&lt;/p> &lt;p>The Visibility binding, however, will only listen to PropertyChanged event, which is not raised when the collection changes.&lt;/p> &lt;p>This means that, although the ItemsControl itself is being updated, its Visibility (and the Visibility of the overlayed TextBlock) are not.&lt;/p> &lt;p>A simple solution is to add an event handler to the CollectionChanged event on the ObservableCollection:&lt;/p> &lt;p>{{&amp;lt; highlight csharp &amp;gt;}} Items.CollectionChanged += Items_CollectionChanged;&lt;/p> &lt;p>private void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { NotifyPropertyChanged(&amp;quot;Items&amp;quot;); } {{&amp;lt; / highlight &amp;gt;}}&lt;/p> &lt;p>This will then refresh the Visibility bindings whenever the ObservableCollection changes, ensuring the updated ItemsControl is actually visible.&lt;/p> &lt;p>Depending on the size of the collection, this may be quite resource-intensive, so YMMV.&lt;/p></content:encoded>
    </item>
    <item>
      <title>Conditional Random Fields for Company Names</title>
      <link>https://hydroxide.dev/articles/conditional-random-fields-for-company-names/</link>
      <description>Let's assume we have a sequence of words, and we want to predict, as accurately as possible, whether each word is a name, verb, or some other part of speech. This is equivalent to predicting a seque</description>
      <pubDate>Thu, 10 May 2018 04:58:40</pubDate>
      <guid isPermaLink="true">https://hydroxide.dev/articles/conditional-random-fields-for-company-names/</guid>
      <content:encoded>&lt;p>Let's assume we have a sequence of words, and we want to predict, as accurately as possible, whether each word is a name, verb, or some other part of speech.&lt;/p> &lt;p>This is equivalent to predicting a sequence of labels (where each label represents a part-of-speech tag) from a sequence of observations (where each observation is a word from a natural language vocabulary).&lt;/p> &lt;p>In mathematical terms, this equates to finding the sequence of labels that is &lt;i>most probable&lt;/i> for the given sequence of words.&lt;/p> &lt;h2>Brute force&lt;/h2> &lt;p>We could do this by determining p(Y|X) - the conditional probability of the label sequence (Y) given the observation sequence (X).&lt;/p> &lt;p>Assuming we had some model that could give us p(X), p(Y) and p(X|Y), we could explicitly calculate this by using Bayes Rule:&lt;/p> &lt;div class="mathblock"> &lt;span>$p(Y|X) = \frac{p(X|Y)p(Y)}{p(X)}$&lt;/span> &lt;/div> &lt;p>Our label prediction would then simply be the sequence with the maximum probability.&lt;/p> &lt;p>This would, however, require us to explicitly model both &lt;span>$p(Y)$&lt;/span> and &lt;span>$p(X)$&lt;/span> - the probability of the observed word sequence (over all possible word sequences), and the probability of every possible label sequence (over all possible label sequences).&lt;/p> &lt;p>This is equivalent to modelling the joint probability of both label and word sequences.&lt;/p> &lt;h2>Reducing the event space&lt;/h2> &lt;p>This is intractable for all but the most simple cases - from the binomial theorem, the number of possible word sequences grows factorially as the sequence grows larger.&lt;/p> &lt;p>This brute force approach is of complexity &lt;span>$O(n!)$&lt;/span>, so we want to find a better approach.&lt;/p> &lt;p>What if we disregarded the probability of the observation itself?&lt;/p> &lt;p>We know intuitively that many combinations of words, even if combinatorially &lt;i>possible&lt;/i>, never actually appear in the English language.&lt;/p> &lt;p>We don't want to waste computational time modelling nonsensical phrases like &amp;quot;trumpet cat the bag overlay&amp;quot;.&lt;/p> &lt;p>Ideally, we would ignore the probability of the observation and consider only &lt;span>$p(Y|X)$&lt;/span>, the probability of each label sequence &lt;i>conditioned on that sequence of words&lt;/i>.&lt;/p> &lt;p>This seems intuitively reasonable for our label prediction task.&lt;/p> &lt;p>We only really care about labelling word sequences that we are given, so avoiding modelling &lt;span>$p(X)$&lt;/span> delivers a significant reduction in computational complexity.&lt;/p> &lt;h2>Assumption of independence&lt;/h2> &lt;p>Could we similarly reduce the complexity of our model for p(Y)?&lt;/p> &lt;p>Without any simplifying assumptions, we would again need to consider every possible sequence of labels - a set that is too large to work with in general.&lt;/p> &lt;p>What if we assumed that each label in the sequence is independent of the others?&lt;/p> &lt;p>This would allow us to focus on the probability of each label in its respective position, separately and independently of anything that comes before or after it.&lt;/p> &lt;p>This would reduce the number of calculations for &lt;span>$p(Y)$&lt;/span> from &amp;quot;all possible K-length sequences from N possible labels&amp;quot; (&lt;span>$\binom{N}{K}$&lt;/span>) to a more manageable (&amp;quot;K independent draws of N possible labels&amp;quot; (NK) - a clear reduction.&lt;/p> &lt;p>The question then becomes whether or not this is a reasonable assumption for our task at hand&lt;sup class="footnote-ref">&lt;a href="#fn-1" id="fnref-1">1&lt;/a>&lt;/sup>.&lt;/p> &lt;p>In English, at least, we know that adjectives tend to appear before nouns, adverbs don't follow prepositions, and so on. The labels are clearly not independent.&lt;/p> &lt;h2>Relaxed assumption of interdepedence&lt;/h2> &lt;p>What if we adopted a more relaxed independence assumption - that each label is conditionally independent, given its immediate predecessors?&lt;/p> &lt;p>For example, say we want to predict the blank in the following sequence:&lt;/p> &lt;div class="mathblock" style="font-size:18px"> NUM ORG VB _ &lt;/div> &lt;p>We want to model the probability of the label at position i, given every preceding label in the sequence:&lt;/p> &lt;div class="mathblock"> &lt;span>$p(y_{i} | y_{i-1}, y_{i-2}, y_{i-3}) $&lt;/span> &lt;/div> &lt;p>which is equivalent to&lt;/p> &lt;div class="mathblock"> &lt;span>$p(y_{i} | y_{i-1}).p(y_{i-1}|y_{i-2}, y_{i-3}).p(y_{i-2}|y_{i-3})$&lt;/span> &lt;/div> &lt;p>If we assume that each label is independent of everything except its immediate predecessor, then:&lt;/p> &lt;div class="mathblock"> &lt;span>$p(y_{i}) = p(y_{i} | y_{i-1})$&lt;/span> &lt;/div> &lt;p>This is a more realistic assumption than naive/strong independence.&lt;/p> &lt;p>Now, we can capture some first-order interactions and interdependence &lt;i>between&lt;/i> labels, which fits much better with our prior knowledge of linguistic structure&lt;sup class="footnote-ref">&lt;a href="#fn-2" id="fnref-2">2&lt;/a>&lt;/sup>.&lt;/p> &lt;p>If we were explicitly modelling the probability of each observation (i.e. p(X)), reintroducing p(Y|X) would give us a Hidden Markov Model, where each label corresponds to the hidden state, and each word in the sequence corresponds to the observation.&lt;/p> &lt;p>Without explicit consideration of consideration of P(X), this then gives us a Conditional Random Field.&lt;/p> &lt;p>This model is equivalent to a Markov &lt;u>Random Field&lt;/u>, &lt;u>conditioned&lt;/u> on a sequence of observations (hence the name - Conditional Random Field).&lt;/p> &lt;p>CRFs are a restricted form of Hidden Markov Model that allow more tractable probability models, by avoiding the need to explicitly consider the probability of the observations.&lt;/p> &lt;p>This is important for our label sequence prediction task, where the probability input space - that is, the space of all possible word sequence combinations - is simply too large to explicitly consider.&lt;/p> &lt;p>For discriminative tasks such as this, CRFs deliver competitive performance by making weaker independence assumptions about a smaller search space.&lt;sup class="footnote-ref">&lt;a href="#fn-3" id="fnref-3">3&lt;/a>&lt;/sup>&lt;/p> &lt;p>So far, I've only talked about &lt;i>what&lt;/i> probability we're trying to model - not &lt;i>how&lt;/i> we might actually model it.&lt;/p> &lt;p>In the next post, I'll give a breakdown of what it actually means to model the probability under a linear chain CRF.&lt;/p> &lt;div class="references"> &lt;h2>References &amp; Further Reading&lt;/h2> &lt;ul> &lt;li>H. Wallach, &lt;i>Conditional Random Fields: An Introduction&lt;/i>, University of Pennsylvania CIS Technical Report MS-CIS-04-21&lt;/li> &lt;li>C. Sutton and A. McCallum, &lt;i>An Introduction to Conditional Random Fields&lt;/i>, Foundations and Trends in Machine Learning, Vol. 4, No. 4 (2011) 267-373&lt;/li> &lt;li>R. Klinger and K. Tomanek, &lt;i>Classical Probabilistic Models and Conditional Random Fields&lt;/i>, Algorithm Engineering Report TR07-2-013 &lt;/li> &lt;/div> &lt;section class="footnotes"> &lt;ol> &lt;li id="fn-1"> &lt;p>Note that this is the strong independence assumption adopted by Naive Bayes. &lt;a href="#fnref-1" class="footnote-backref">↩&lt;/a>&lt;/p> &lt;/li> &lt;li id="fn-2"> &lt;p>This has also been explored empirically - see R. Caruana and A. Niculescu-Mizil, “An empirical comparison of supervised learning algorithms using different performance metrics,” Technical Report TR2005-1973, Cornell University, 2005. &lt;a href="#fnref-2" class="footnote-backref">↩&lt;/a>&lt;/p> &lt;/li> &lt;li id="fn-3"> &lt;p>The downside is that, by not modelling &lt;span>$p(X)$&lt;/span>, we can no longer calculate &lt;span>$p(X|Y)$&lt;/span>. This means we can only assign labels to words (a classification/discriminative objective), and we are unable to create a generative model that can generate the most likely word given a label. &lt;a href="#fnref-3" class="footnote-backref">↩&lt;/a>&lt;/p> &lt;/li> &lt;/ol> &lt;/section></content:encoded>
    </item>
  </channel>
</rss>