The personal website of Scott W Harden
March 27th, 2021

WSPR Code Generator

WSPR (Weak Signal Propagation Reporter) is a protocol for weak-signal radio communication. Transmissions encode a station's callsign, location (grid square), and transmitter power into a frequency-shift-keyed (FSK) signal that hops between 4 frequencies to send 162 tones in just under two minutes. Signals can be decoded with S/N as low as −34 dB! WSJT-X, the most commonly-used decoding software, reports decoded signals to wsprnet.org so propagation can be assessed all over the world.

WsprSharp is a WSPR encoding library implemented in C#. I created this library learn more about the WSPR protocol. I also made a web application (using Blazor WebAssembly) to make it easy to generate WSPR transmissions online: WSPR Code Generator

WSPR Transmission Information

  • One packet is 110.6 sec continuous wave
  • Frequency shifts between 4 tones
  • Each tone keys for 0.683 sec
  • Tones are separated by 1.4648 Hz
  • Total bandwidth is about 6 Hz
  • 50 bits of information are packaged into a 162 bit message with FEC
  • Transmissions always begin 1 sec after even minutes (UTC)

Resources

Markdown source code last modified on September 12th, 2021
---
Title: WSPR Code Generator
Description: An online resource for generating encoded WSPR messages
Date: 2021-03-27 8PM EST
Tags: blazor, amateur radio, qrss
---

# WSPR Code Generator

**[WSPR](https://en.wikipedia.org/wiki/WSPR_(amateur_radio_software)) (Weak Signal Propagation Reporter) is a protocol for weak-signal radio communication.** Transmissions encode a station's callsign, location (grid square), and transmitter power into a frequency-shift-keyed (FSK) signal that hops between 4 frequencies to send 162 tones in just under two minutes. Signals can be decoded with S/N as low as −34 dB! [WSJT-X](https://physics.princeton.edu/pulsar/k1jt/wsjtx.html), the most commonly-used decoding software, reports decoded signals to [wsprnet.org](https://wsprnet.org/) so propagation can be assessed all over the world.

**[WsprSharp](https://github.com/swharden/WsprSharp) is a WSPR encoding library implemented in C#.** I created this library learn more about the WSPR protocol. I also made a web application (using Blazor WebAssembly) to make it easy to generate WSPR transmissions online: [WSPR Code Generator](https://swharden.com/software/wspr-code-generator)

[![](wspr-code-generator.png)](https://swharden.com/software/wspr-code-generator)


### WSPR Transmission Information

![](wspr-spectrogram.png)

* One packet is 110.6 sec continuous wave
* Frequency shifts between 4 tones 
* Each tone keys for 0.683 sec
* Tones are separated by 1.4648 Hz
* Total bandwidth is about 6 Hz
* 50 bits of information are packaged into a 162 bit message with [FEC](https://en.wikipedia.org/wiki/Error_correction_code)
* Transmissions always begin 1 sec after even minutes (UTC)

### Resources
* [WSPR Code Generator](https://swharden.com/software/wspr-code-generator) - encode WSPR messages from your browser
* [WsprSharp](https://github.com/swharden/WsprSharp) - a .NET library for encoding and decoding WSPR transmissions using C#
* [FSKview](https://swharden.com/software/FSKview/) - spectrogram for viewing frequency-shift keyed (FSK) signals in real time
* [Anatomy of a WSPR transmission](https://swharden.com/software/FSKview/wspr/)
* [WSPR](https://en.wikipedia.org/wiki/WSPR_(amateur_radio_software)) on Wikipedia
* [K1JT Program](http://physics.princeton.edu/pulsar/K1JT/devel.html)
* [Grid square lookup](http://www.levinecentral.com/ham/grid_square.php?Grid=FN20)
* [WSJT](https://sourceforge.net/projects/wsjt/) on SourceForge
* [WSPR Mode in WSJT-X](https://wsprnet.org/drupal/node/5563)
January 31st, 2021

Reflection and XML Documentation in C#

In C#, you can document your code using XML directly before code blocks. This XML documentation is used by Visual Studio to display tooltips and provide autocomplete suggestions.

/// <summary>
///  This method performs an important function.
/// </summary>
public void MyMethod() {}

To enable automatic generation of XML documentation on every build, add the following to your csproj file:

<PropertyGroup>
  <DocumentationFile>MyProgram.xml</DocumentationFile>
</PropertyGroup>

However, XML documentation is not actually metadata, so it is not available in the compiled assembly. In this post I'll show how you can use System.Reflection to gather information about methods and combine it with documentation from an XML file read with System.Xml.Linq.XDocument.

I find this useful for writing code which automatically generates documentation. Reflection alone cannot provide comments, and XML documentation alone cannot provide parameter names (just their types). By combining reflection with XML documentation, we can more completely describe methods in C# programs.

⚠️ WARNING: These code examples are intentionally simple. They only demonstrate how to read summary comments from XML documentation for methods found using reflection. Additional functionality can be added as needed, and my intent here is to provide a simple starting point rather than overwhelmingly complex examples that support all features and corner cases.

💡 Source code can be downloaded at the bottom of this article

✔️ The "hack" described on this page is aimed at overcoming limitations of partially-documented XML. A better solution is to fully document your code, in which case the XML document is self-sufficient. The primary goal of these efforts is to use XML documentation where it is present, but use Reflection to fill in the blanks about methods which are undocumented or only partially documented. Perhaps a better strategy would be to have a fully documented code base, with an XML file containing <summary>, <returns>, and <param> for every parameter. Tests can ensure this is and remains the case.

What does the XML Documentation look like?

There are some nuances here you might not expect, especially related to arrays, generics, and nullables. Let's start with a demo class with documented summaries. Keep in mind that the goal of this project is to help use Reflection to fill in the blanks about undocumented or partially-documented code, so this example will only add a <summary> but no <param> descriptions.

DemoClass.cs

/// <summary>
/// Display a name
/// </summary>
public static void ShowName(string name)
{
    Console.WriteLine($"Hi {name}");
}

/// <summary>
/// Display a name a certain number of times
/// </summary>
public static void ShowName(string name, byte repeats)
{
    for (int i = 0; i < repeats; i++)
        Console.WriteLine($"Hi {name}");
}

/// <summary>
/// Display the type of the variable passed in
/// </summary>
public static void ShowGenericType<T>(T myVar)
{
    Console.WriteLine($"Generic type {myVar.GetType()}");
}

/// <summary>
/// Display the value of a nullable integer
/// </summary>
public static void ShowNullableInt(int? myInt)
{
    Console.WriteLine(myInt);
}

XML Documentation File

There are a few important points to notice here:

  • Each method is a member with a name starting with M:
  • Parameter types are in the member name, but not parameter names!
  • Parameters might be listed in the XML, but they will be missing if only <summary> was added in code
  • 💡 The key step required to connect a reflected method with its XML documentation is being able to determine the XML method name of that method. How to do this is discussed below...
<?xml version="1.0"?>
<doc>
    <assembly>
        <name>XmlDocDemo</name>
    </assembly>
    <members>
        <member name="M:XmlDocDemo.DemoClass.ShowName(System.String)">
            <summary>
            Display a name
            </summary>
        </member>
        <member name="M:XmlDocDemo.DemoClass.ShowName(System.String,System.Byte)">
            <summary>
            Display a name a certain number of times
            </summary>
        </member>
        <member name="M:XmlDocDemo.DemoClass.ShowGenericType``1(``0)">
            <summary>
            Display the type of the variable passed in
            </summary>
        </member>
        <member name="M:XmlDocDemo.DemoClass.ShowNullableInt(System.Nullable{System.Int32})">
            <summary>
            Display the value of a nullable integer
            </summary>
        </member>
    </members>
</doc>

XML Name Details

  • Generics from types have a single ` character
  • Generics from methods have double `` characters
  • If the parameter is by "ref" then you need to pre-pend the @ character
  • If the parameter is a pointer you need to pre-pend it with the * character
  • If the parameter is an array, you need to add [] characters and the appropriate number of commas
  • If the parameter is Nullable it will be wrapped in System.Nullable{}
  • If the method is MethodInfo is a casing operator, then you need to pre-pend it with ~ character

Thanks Zachary Patten for sharing these details in an MSDN article and e-mail correspondence

Read XML Documentation File

This code reads the XML documentation file (using the modern XDocument) and stores method summaries in a Dictionary using the XML method name as a key. This dictionary will be accessed later to look-up documentation for methods found using Reflection.

private readonly Dictionary<string, string> MethodSummaries = new Dictionary<string, string>();

public XmlDoc(string xmlFile)
{
    XDocument doc = XDocument.Load(xmlFile);
    foreach (XElement element in doc.Element("doc").Element("members").Elements())
    {
        string xmlName = element.Attribute("name").Value;
        string xmlSummary = element.Element("summary").Value.Trim();
        MethodSummaries[xmlName] = xmlSummary;
    }
}

Determine XML Method Name for a Reflected Method

This example code returns the XML member name for a method found by reflection. This is the key step required to connect reflected methods with their descriptions in XML documentation files.

⚠️ Warning: This code sample may not support all corner-cases, but in practice I found it supports all of the ones I typically encounter in my code bases and it's a pretty good balance between functionality and simplicity.

public static string GetXmlName(MethodInfo info)
{
    string declaringTypeName = info.DeclaringType.FullName;

    if (declaringTypeName is null)
        throw new NotImplementedException("inherited classes are not supported");

    string xmlName = "M:" + declaringTypeName + "." + info.Name;
    xmlName = string.Join("", xmlName.Split(']').Select(x => x.Split('[')[0]));
    xmlName = xmlName.Replace(",", "");

    if (info.IsGenericMethod)
        xmlName += "``#";

    int genericParameterCount = 0;
    List<string> paramNames = new List<string>();
    foreach (var parameter in info.GetParameters())
    {
        Type paramType = parameter.ParameterType;
        string paramName = GetXmlNameForMethodParameter(paramType);
        if (paramName.Contains("#"))
            paramName = paramName.Replace("#", (genericParameterCount++).ToString());
        paramNames.Add(paramName);
    }
    xmlName = xmlName.Replace("#", genericParameterCount.ToString());

    if (paramNames.Any())
        xmlName += "(" + string.Join(",", paramNames) + ")";

    return xmlName;
}

private static string GetXmlNameForMethodParameter(Type type)
{
    string xmlName = type.FullName ?? type.BaseType.FullName;
    bool isNullable = xmlName.StartsWith("System.Nullable");
    Type nullableType = isNullable ? type.GetGenericArguments()[0] : null;

    // special formatting for generics (also Func, Nullable, and ValueTulpe)
    if (type.IsGenericType)
    {
        var genericNames = type.GetGenericArguments().Select(x => GetXmlNameForMethodParameter(x));
        var typeName = type.FullName.Split('`')[0];
        xmlName = typeName + "{" + string.Join(",", genericNames) + "}";
    }

    // special case for generic nullables
    if (type.IsGenericType && isNullable && type.IsArray == false)
        xmlName = "System.Nullable{" + nullableType.FullName + "}";

    // special case for multidimensional arrays
    if (type.IsArray && (type.GetArrayRank() > 1))
    {
        string arrayName = type.FullName.Split('[')[0].Split('`')[0];
        if (isNullable)
            arrayName += "{" + nullableType.FullName + "}";
        string arrayContents = string.Join(",", Enumerable.Repeat("0:", type.GetArrayRank()));
        xmlName = arrayName + "[" + arrayContents + "]";
    }

    // special case for generic arrays
    if (type.IsArray && type.FullName is null)
        xmlName = "``#[]";

    // special case for value types
    if (xmlName.Contains("System.ValueType"))
        xmlName = "`#";

    return xmlName;
}

Get XML Documentation for a Reflected Method

Now that we have XmlName(), we can easily iterate through reflected methods and get their XML documentation.

// use Reflection to get info from custom methods
var infos = typeof(DemoClass).GetMethods()
                             .Where(x => x.DeclaringType.FullName != "System.Object")
                             .ToArray();

// display XML info about each reflected method
foreach (MethodInfo mi in infos)
{
    string xmlName = XmlName(mi);
    Console.WriteLine("");
    Console.WriteLine("Method: " + XmlDoc.MethodSignature(mi));
    Console.WriteLine("XML Name: " + xmlName);
    Console.WriteLine("XML Summary: " + MethodSummaries[xmlName]);
}

Output

Method: XmlDocDemo.DemoClass.ShowName(string name)
XML Name: M:XmlDocDemo.DemoClass.ShowName(System.String)
XML Summary: Display a name

Method: XmlDocDemo.DemoClass.ShowName(string name, byte repeats)
XML Name: M:XmlDocDemo.DemoClass.ShowName(System.String,System.Byte)
XML Summary: Display a name a certain number of times

Method: XmlDocDemo.DemoClass.ShowGenericType<T>(T myVar)
XML Name: M:XmlDocDemo.DemoClass.ShowGenericType``1(``0)
XML Summary: Display the type of the variable passed in

Method: XmlDocDemo.DemoClass.ShowNullableInt(int? myInt)
XML Name: M:XmlDocDemo.DemoClass.ShowNullableInt(System.Nullable{System.Int32})
XML Summary: Display the value of a nullable integer

Resources

Source Code

A simple-case working demo of these concepts can be downloaded here:

Documentation Generators

  • DocFX - An extensible and scalable static documentation generator.

  • Sandcastle - Sandcastle Help File Builder (SHFB). A standalone GUI, Visual Studio integration package, and MSBuild tasks providing full configuration and extensibility for building help files with the Sandcastle tools.

Zachary Patten's Useful Article

There is an extensive article on this topic in the October 2019 issue of MSDN Magazine, Accessing XML Documentation via Reflection by Zachary Patten. The code examples there provide a lot of advanced features, but are technically incomplete and some critical components are only shown using pseudocode. The reader is told that full code is available as part of the author's library Towel, but this library is extensive and provides many functions unrelated to reflection and XML documentation making it difficult to navigate. The method to convert a method to its XML documentation name is Towel/Meta.cs#L1026-L1092, but it's coupled to other code which requires hash maps to be pre-formed in order to use it. My post here is intended to be self-contained simple reference for how to combine XML documentation with Reflection, but users interested in reading further on this topic are encouraged to read Zachary's article.

Update: Potentially Useful Libraries

Update (Feb 21, 2021): I continued to do research on this topic. I thought I'd find a "golden bullet" library that could help me do this perfectly. The code above does a pretty good job, but I would feel more confident using something designed/tested specifically for this task. I looked and found some helpful libraries, but none of them met all me needs. For my projects, I decided just to use the code above.

DocXml

DocXml is a small .NET standard 2.0 library of helper classes and methods for compiler-generated XML documentation retrieval. Its API is very simple and easy to use with a predictive IDE. Out of the box though it was unable to properly identify the XML name of one of my functions. I think it got stuck on the generic method with a multi-dimensional generic array as an argument, but don't recall for sure. For basic code bases, this looks like a fantastic library.

NuDoq

NuDoq (previously NuDoc?) is a standalone API to read and write .NET XML documentation files and optionally augment it with reflection information. According to the releases it was actively worked on around 2014, then rested quietly for a few years and new releases began in 2021. NuDoq looks quite extensive, but takes some studying before it can be used effectively. "Given the main API to traverse and act on the documentation elements is through the visitor pattern, the most important part of the API is knowing the types of nodes/elements in the visitable model."

Towel

Towel is a .NET library intended to add core functionality and make advanced topics as clean and simple as possible. Towel has tools for working with data structures, algorithms, mathematics, metadata, extensions, console, and more. Towel favors newer coding practices over maintaining backwards compatibility, and at the time of writing Towel only supports .NET 5 and newer. One of Towel's Meta module has methods to get XML names for reflected objects. It's perfect, but requires .NET 5.0 or newer so I could not use it in my project.

Washcloth

I tried to create a version of Towel that isolated just the XML documentation reflection module so I could back-port it to .NET Standard. I created Washcloth which largely achieved this and wrapped Towel behind a simple API. This task proved extremely difficult to accomplish cleanly though, because most modules in the Towel code base are highly coupled to one another so it was very difficult to isolate the Meta module and I never achieved this goal to my satisfaction. I named the project Washcloth because a washcloth is really just a small towel with less functionality. Washcloth technically works (and can be used in projects back to .NET Core 2.0 and .NET Framework 4.6.1), but so much coupled code from the rest of Towel came along with it that I decided not to use this project for my application.

Final Thoughts

After a month poking around at this, here's where I landed:

  • Reading XML documentation is easy with System.Xml.Linq.XDocument

  • Getting XML names for fields, properties, classes, constructors, and enumerations is easy

  • Getting XML names for methods can be very hard

  • If you're creating something special, consider a custom solution like that shown above (~50 lines and you're done).

  • If you can target the latest .NET platform, consider the Meta module in Towel

  • If you want a package that targets .NET Standard, consider DocXml (simple) or NuDoq (complex)

Markdown source code last modified on February 22nd, 2021
---
Title: Reflection and XML Documentation in C# 
Description: How to access XML documentation from within a C# application
Date: 2021-01-31 1:57pm
Tags: csharp
---

# Reflection and XML Documentation in C# 

**In C#, you can document your code using XML directly before code blocks.** This XML documentation is used by Visual Studio to display tooltips and provide autocomplete suggestions. 

```cs
/// <summary>
///  This method performs an important function.
/// </summary>
public void MyMethod() {}
```

**To enable automatic generation of XML documentation** on every build, add the following to your csproj file:

```xml
<PropertyGroup>
  <DocumentationFile>MyProgram.xml</DocumentationFile>
</PropertyGroup>
```

**However, XML documentation is not actually metadata, so it is not available in the compiled assembly.** In this post I'll show how you can use `System.Reflection` to gather information about methods and combine it with documentation from an XML file read with `System.Xml.Linq.XDocument`. 

**I find this useful for writing code which automatically generates documentation.** Reflection alone cannot provide comments, and XML documentation alone cannot provide parameter names (just their types). By combining reflection with XML documentation, we can more completely describe methods in C# programs.

> **⚠️ WARNING: These code examples are intentionally simple.** They only demonstrate how to read summary comments from XML documentation for methods found using reflection. Additional functionality can be added as needed, and my intent here is to provide a simple starting point rather than overwhelmingly complex examples that support all features and corner cases.

> **💡 Source code** can be downloaded at the bottom of this article

> **✔️ The "hack" described on this page is aimed at overcoming limitations of partially-documented XML. A better solution is to fully document your code, in which case the XML document is self-sufficient.** The primary goal of these efforts is to use XML documentation where it is present, but use Reflection to fill in the blanks about methods which are undocumented or only partially documented. Perhaps a better strategy would be to have a fully documented code base, with an XML file containing `<summary>`, `<returns>`, and `<param>` for every parameter. Tests can ensure this is and remains the case.

## What does the XML Documentation look like?

There are some nuances here you might not expect, especially related to arrays, generics, and nullables. Let's start with a demo class with documented summaries. Keep in mind that the goal of this project is to help use Reflection to fill in the blanks about undocumented or partially-documented code, so this example will only add a `<summary>` but no `<param>` descriptions.

### DemoClass.cs

```cs
/// <summary>
/// Display a name
/// </summary>
public static void ShowName(string name)
{
    Console.WriteLine($"Hi {name}");
}

/// <summary>
/// Display a name a certain number of times
/// </summary>
public static void ShowName(string name, byte repeats)
{
    for (int i = 0; i < repeats; i++)
        Console.WriteLine($"Hi {name}");
}

/// <summary>
/// Display the type of the variable passed in
/// </summary>
public static void ShowGenericType<T>(T myVar)
{
    Console.WriteLine($"Generic type {myVar.GetType()}");
}

/// <summary>
/// Display the value of a nullable integer
/// </summary>
public static void ShowNullableInt(int? myInt)
{
    Console.WriteLine(myInt);
}
```

### XML Documentation File

There are a few important points to notice here:

* Each method is a `member` with a `name` starting with `M:`
* Parameter _types_ are in the member name, but not parameter _names_!
* Parameters might be listed in the XML, but they will be missing if only `<summary>` was added in code
* 💡 The key step required to connect a reflected method with its XML documentation is being able to determine the XML method name of that method. How to do this is discussed below...

```xml
<?xml version="1.0"?>
<doc>
    <assembly>
        <name>XmlDocDemo</name>
    </assembly>
    <members>
        <member name="M:XmlDocDemo.DemoClass.ShowName(System.String)">
            <summary>
            Display a name
            </summary>
        </member>
        <member name="M:XmlDocDemo.DemoClass.ShowName(System.String,System.Byte)">
            <summary>
            Display a name a certain number of times
            </summary>
        </member>
        <member name="M:XmlDocDemo.DemoClass.ShowGenericType``1(``0)">
            <summary>
            Display the type of the variable passed in
            </summary>
        </member>
        <member name="M:XmlDocDemo.DemoClass.ShowNullableInt(System.Nullable{System.Int32})">
            <summary>
            Display the value of a nullable integer
            </summary>
        </member>
    </members>
</doc>
```

### XML Name Details

* Generics from types have a single <code>`</code> character 
* Generics from methods have double <code>``</code> characters
* If the parameter is by "ref" then you need to pre-pend the `@` character
* If the parameter is a pointer you need to pre-pend it with the <code>*</code> character
* If the parameter is an array, you need to add `[]` characters and the appropriate number of commas
* If the parameter is Nullable it will be wrapped in `System.Nullable{}`
* If the method is MethodInfo is a casing operator, then you need to pre-pend it with `~` character

_Thanks [Zachary Patten](https://github.com/ZacharyPatten) for sharing these details in an [MSDN article](https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection) and e-mail correspondence_

## Read XML Documentation File

This code reads the XML documentation file (using the modern [XDocument](https://docs.microsoft.com/en-us/dotnet/api/system.xml.linq.xdocument)) and stores method summaries in a Dictionary using the XML method name as a key. This dictionary will be accessed later to look-up documentation for methods found using Reflection.

```cs
private readonly Dictionary<string, string> MethodSummaries = new Dictionary<string, string>();

public XmlDoc(string xmlFile)
{
    XDocument doc = XDocument.Load(xmlFile);
    foreach (XElement element in doc.Element("doc").Element("members").Elements())
    {
        string xmlName = element.Attribute("name").Value;
        string xmlSummary = element.Element("summary").Value.Trim();
        MethodSummaries[xmlName] = xmlSummary;
    }
}
```

## Determine XML Method Name for a Reflected Method

This example code returns the XML member name for a method found by reflection. **This is the key step** required to connect reflected methods with their descriptions in XML documentation files.

**⚠️ Warning: This code sample may not support all corner-cases**, but in practice I found it supports all of the ones I typically encounter in my code bases and it's a pretty good balance between functionality and simplicity.

```cs
public static string GetXmlName(MethodInfo info)
{
	string declaringTypeName = info.DeclaringType.FullName;

	if (declaringTypeName is null)
		throw new NotImplementedException("inherited classes are not supported");

	string xmlName = "M:" + declaringTypeName + "." + info.Name;
	xmlName = string.Join("", xmlName.Split(']').Select(x => x.Split('[')[0]));
	xmlName = xmlName.Replace(",", "");

	if (info.IsGenericMethod)
		xmlName += "``#";

	int genericParameterCount = 0;
	List<string> paramNames = new List<string>();
	foreach (var parameter in info.GetParameters())
	{
		Type paramType = parameter.ParameterType;
		string paramName = GetXmlNameForMethodParameter(paramType);
		if (paramName.Contains("#"))
			paramName = paramName.Replace("#", (genericParameterCount++).ToString());
		paramNames.Add(paramName);
	}
	xmlName = xmlName.Replace("#", genericParameterCount.ToString());

	if (paramNames.Any())
		xmlName += "(" + string.Join(",", paramNames) + ")";

	return xmlName;
}

private static string GetXmlNameForMethodParameter(Type type)
{
	string xmlName = type.FullName ?? type.BaseType.FullName;
	bool isNullable = xmlName.StartsWith("System.Nullable");
	Type nullableType = isNullable ? type.GetGenericArguments()[0] : null;

	// special formatting for generics (also Func, Nullable, and ValueTulpe)
	if (type.IsGenericType)
	{
		var genericNames = type.GetGenericArguments().Select(x => GetXmlNameForMethodParameter(x));
		var typeName = type.FullName.Split('`')[0];
		xmlName = typeName + "{" + string.Join(",", genericNames) + "}";
	}

	// special case for generic nullables
	if (type.IsGenericType && isNullable && type.IsArray == false)
		xmlName = "System.Nullable{" + nullableType.FullName + "}";

	// special case for multidimensional arrays
	if (type.IsArray && (type.GetArrayRank() > 1))
	{
		string arrayName = type.FullName.Split('[')[0].Split('`')[0];
		if (isNullable)
			arrayName += "{" + nullableType.FullName + "}";
		string arrayContents = string.Join(",", Enumerable.Repeat("0:", type.GetArrayRank()));
		xmlName = arrayName + "[" + arrayContents + "]";
	}

	// special case for generic arrays
	if (type.IsArray && type.FullName is null)
		xmlName = "``#[]";

	// special case for value types
	if (xmlName.Contains("System.ValueType"))
		xmlName = "`#";

	return xmlName;
}
```

## Get XML Documentation for a Reflected Method

Now that we have `XmlName()`, we can easily iterate through reflected methods and get their XML documentation.

```cs
// use Reflection to get info from custom methods
var infos = typeof(DemoClass).GetMethods()
                             .Where(x => x.DeclaringType.FullName != "System.Object")
                             .ToArray();

// display XML info about each reflected method
foreach (MethodInfo mi in infos)
{
    string xmlName = XmlName(mi);
    Console.WriteLine("");
    Console.WriteLine("Method: " + XmlDoc.MethodSignature(mi));
    Console.WriteLine("XML Name: " + xmlName);
    Console.WriteLine("XML Summary: " + MethodSummaries[xmlName]);
}
```

### Output

```
Method: XmlDocDemo.DemoClass.ShowName(string name)
XML Name: M:XmlDocDemo.DemoClass.ShowName(System.String)
XML Summary: Display a name

Method: XmlDocDemo.DemoClass.ShowName(string name, byte repeats)
XML Name: M:XmlDocDemo.DemoClass.ShowName(System.String,System.Byte)
XML Summary: Display a name a certain number of times

Method: XmlDocDemo.DemoClass.ShowGenericType<T>(T myVar)
XML Name: M:XmlDocDemo.DemoClass.ShowGenericType``1(``0)
XML Summary: Display the type of the variable passed in

Method: XmlDocDemo.DemoClass.ShowNullableInt(int? myInt)
XML Name: M:XmlDocDemo.DemoClass.ShowNullableInt(System.Nullable{System.Int32})
XML Summary: Display the value of a nullable integer
```

## Resources

### Source Code

A simple-case working demo of these concepts can be downloaded here:

* [**XmlDocDemo.zip**](XmlDocDemo.zip) (4kb)

### Documentation Generators

* [DocFX](https://dotnet.github.io/docfx/) - An extensible and scalable static documentation generator.

* [Sandcastle](https://github.com/EWSoftware/SHFB) - Sandcastle Help File Builder (SHFB). A standalone GUI, Visual Studio integration package, and MSBuild tasks providing full configuration and extensibility for building help files with the Sandcastle tools.

### Zachary Patten's Useful Article

There is an extensive article on this topic in the October 2019 issue of MSDN Magazine, [Accessing XML Documentation via Reflection](https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/october/csharp-accessing-xml-documentation-via-reflection) by [Zachary Patten](https://github.com/ZacharyPatten). The code examples there provide a lot of advanced features, but are technically incomplete and some critical components are only shown using pseudocode. The reader is told that full code is available as part of the author's library [Towel](https://github.com/ZacharyPatten/Towel), but this library is extensive and provides many functions unrelated to reflection and XML documentation making it difficult to navigate. The method to convert a method to its XML documentation name is [Towel/Meta.cs#L1026-L1092](https://github.com/ZacharyPatten/Towel/blob/360b4ae695c5f95ca9b8e1ec3c466092eeff972e/Sources/Towel/Meta.cs#L1026-L1092), but it's coupled to other code which requires hash maps to be pre-formed in order to use it. My post here is intended to be self-contained simple reference for how to combine XML documentation with Reflection, but users interested in reading further on this topic are encouraged to read Zachary's article.

## Update: Potentially Useful Libraries

**Update (Feb 21, 2021):** I continued to do research on this topic. I thought I'd find a "golden bullet" library that could help me do this perfectly. The code above does a pretty good job, but I would feel more confident using something designed/tested specifically for this task. I looked and found some helpful libraries, but none of them met all me needs. For my projects, I decided just to use the code above.

### DocXml

[DocXml](https://github.com/loxsmoke/DocXml) is a small .NET standard 2.0 library of helper classes and methods for compiler-generated XML documentation retrieval. Its API is very simple and easy to use with a predictive IDE. Out of the box though it was unable to properly identify the XML name of one of my functions. I think it got stuck on the generic method with a multi-dimensional generic array as an argument, but don't recall for sure. For basic code bases, this looks like a fantastic library.

### NuDoq

[NuDoq](https://github.com/devlooped/NuDoq) (previously NuDoc?) is a standalone API to read and write .NET XML documentation files and optionally augment it with reflection information. According to the [releases](https://github.com/devlooped/NuDoq/releases) it was actively worked on around 2014, then rested quietly for a few years and new releases began in 2021. NuDoq looks quite extensive, but takes some studying before it can be used effectively. "Given the main API to traverse and act on the documentation elements is through the visitor pattern, the most important part of the API is knowing the types of nodes/elements in the visitable model."

### Towel

[Towel](https://github.com/ZacharyPatten/Towel) is a .NET library intended to add core functionality and make advanced topics as clean and simple as possible. Towel has tools for working with data structures, algorithms, mathematics, metadata, extensions, console, and more. Towel favors newer coding practices over maintaining backwards compatibility, and at the time of writing Towel only supports .NET 5 and newer. One of Towel's `Meta` module has methods to get XML names for reflected objects. It's perfect, but requires .NET 5.0 or newer so I could not use it in my project.

### Washcloth

I tried to create a version of Towel that isolated just the XML documentation reflection module so I could back-port it to .NET Standard. I created [Washcloth](https://github.com/swharden/Washcloth) which largely achieved this and wrapped Towel behind a simple API. This task proved extremely difficult to accomplish cleanly though, because most modules in the Towel code base are highly coupled to one another so it was very difficult to isolate the `Meta` module and I never achieved this goal to my satisfaction. I named the project Washcloth because a washcloth is really just a small towel with less functionality. Washcloth technically works (and can be used in projects back to .NET Core 2.0 and .NET Framework 4.6.1), but so much coupled code from the rest of Towel came along with it that I decided not to use this project for my application.

## Final Thoughts

After a month poking around at this, here's where I landed:

* Reading XML documentation is easy with `System.Xml.Linq.XDocument`

* Getting XML names for fields, properties, classes, constructors, and enumerations is easy

* Getting XML names for methods can be very hard

* If you're creating something special, consider a custom solution like that shown above (~50 lines and you're done).

* If you can target the latest .NET platform, consider the `Meta` module in [Towel](https://github.com/ZacharyPatten/Towel)

* If you want a package that targets .NET Standard, consider [DocXml](https://github.com/loxsmoke/DocXml) (simple) or [NuDoq](https://github.com/devlooped/NuDoq) (complex)
January 28th, 2021

Symbolic Links and Directory URLs in PHP

My website went down for a few hours today when my hosting company unexpectedly changed Apache's root http folder to a new one containing a symbolic link. This change broke some of my PHP scripts because __DIR__ suddenly had a base path that was different than DOCUMENT_ROOT, and the str_replace method I was using to determine the directory URL assumed they would always be the same. If you Google "how to get the URL of the current directory with PHP", you'll probably find recommendations to use code like this:

// WARNING: DON'T USE THIS CODE!
$folderUrl = 'http://'.$_SERVER['HTTP_HOST'].str_replace($_SERVER['DOCUMENT_ROOT'], '', __DIR__); 

Let's look at what these variables resolve to on my site:

Variable Value
$_SERVER['HTTP_HOST'] swharden.com
__DIR__ /home/customer/www/swharden.com/public_html/tmp/test
realpath(__DIR__) /home/customer/www/swharden.com/public_html/tmp/test
$_SERVER['DOCUMENT_ROOT'] /home/u123-bsasdfas7hj/www/swharden.com/public_html
realpath($_SERVER['DOCUMENT_ROOT']) /home/customer/www/swharden.com/public_html

Comparing __DIR__ with DOCUMENT_ROOT, notice that without real path resolution the base paths are different! This caused the string replace method to abruptly fail on my website, taking several sites down for a few hours. I'll accept it as a rookie mistake on my part, and I'm sharing what I learned here in case it helps others in the future.

The Solution is to ensure all paths are converted to canonicalized absolute paths using realpath(). This will protect you from unexpected symbolic links. Notice this code also adds the appropriate HTTP or HTTPS prefix.

// This script displays the URL of the current folder
$realDocRoot = realpath($_SERVER['DOCUMENT_ROOT']);
$realDirPath = realpath(__DIR__);
$suffix = str_replace($realDocRoot, '', $realDirPath);
$prefix = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
$folderUrl = $prefix . $_SERVER['HTTP_HOST'] . $suffix;
echo $folderUrl;
Markdown source code last modified on March 19th, 2021
---
Title: Get URL of a Directory with PHP
Description: This page describes how to determine the URL for a given folder using PHP
Date: 2021-01-28 6:15PM
Tags: php
---

# Symbolic Links and Directory URLs in PHP

**My website went down for a few hours today** when my hosting company unexpectedly changed Apache's root http folder to a new one containing a symbolic link. This change broke some of my PHP scripts because `__DIR__` suddenly had a base path that was different than `DOCUMENT_ROOT`, and the `str_replace` method I was using to determine the directory URL assumed they would always be the same. If you Google "how to get the URL of the current directory with PHP", you'll probably find recommendations to use code like this:

```php
// WARNING: DON'T USE THIS CODE!
$folderUrl = 'http://'.$_SERVER['HTTP_HOST'].str_replace($_SERVER['DOCUMENT_ROOT'], '', __DIR__); 
```

Let's look at what these variables resolve to on my site:

Variable | Value
---|---
`$_SERVER['HTTP_HOST']`|`swharden.com`
`__DIR__`|`/home/customer/www/swharden.com/public_html/tmp/test`
`realpath(__DIR__)`|`/home/customer/www/swharden.com/public_html/tmp/test`
`$_SERVER['DOCUMENT_ROOT']`|`/home/u123-bsasdfas7hj/www/swharden.com/public_html`
`realpath($_SERVER['DOCUMENT_ROOT'])`|`/home/customer/www/swharden.com/public_html`

Comparing `__DIR__` with `DOCUMENT_ROOT`, notice that without real path resolution the base paths are different! This caused the string replace method to abruptly fail on my website, taking several sites down for a few hours. I'll accept it as a rookie mistake on my part, and I'm sharing what I learned here in case it helps others in the future.

**The Solution is to ensure all paths** are converted to canonicalized absolute paths using `realpath()`. This will protect you from unexpected symbolic links. Notice this code also adds the appropriate HTTP or HTTPS prefix.

```php
// This script displays the URL of the current folder
$realDocRoot = realpath($_SERVER['DOCUMENT_ROOT']);
$realDirPath = realpath(__DIR__);
$suffix = str_replace($realDocRoot, '', $realDirPath);
$prefix = isset($_SERVER['HTTPS']) ? 'https://' : 'http://';
$folderUrl = $prefix . $_SERVER['HTTP_HOST'] . $suffix;
echo $folderUrl;
```
January 13th, 2021

Liquid Junction Potential (LJP) Theory and Correction

This page contains notes about the analytical methods LJPcalc uses to calculate LJP from ion tables as well as notes for experimenters about what causes LJP and how to compensate for it in electrophysiology experiments.

💡 Use LJPcalc to calculate Liquid Junction Potential (LJP) in your browser

Table of Contents

    References

LJP Calculation Notes

LJPcalc Calculation Method

LJPcalc calculates the liquid junction potential according to the stationary Nernst-Planck equation which is typically regarded as superior to the simpler Henderson equation used by most commercial LJP calculators. Both equations produce nearly identical LJPs, but the Henderson equation becomes inaccurate as ion concentrations increase, and also when calculating LJP for solutions containing polyvalent ions.

LJPcalc Ion Mobility Library

LJPcalc uses an extensive ion mobility library

The ion mobility table is stored in Markdown format. Not only does Markdown make it easy to display the table nicely in a browser, but it also makes the table easy to edit in any text editor. Users desiring to use their own ion mobilities or add new ions to the table can do so by editing the IonTable.md file adjacent to LJPcalc.exe as needed.

Influence of Ion Sequence on Calculated LJP

💡 LJPcalc automatically sorts the ion table into an ideal sequence prior to solving for LJP. Attention only needs to be paid to the ion sequence if automatic sorting is disabled.

When calculating LJP for a set of ions it is important to consider the sequence in which they are listed. Additional information can be found in Marino et al., 2014 which describes the exact computational methods employed by LJPcalc.

  • The last ion's c0 may be overridden to achieve electroneutrality on the c0 side. This will not occur if the sum of charge on the c0 side is zero.

  • cL for most ions will be slightly adjusted to achieve electroneutrality on the cL side. The second-to-last ion's cL (which cannot equal its c0) will remain fixed, while the last cL will be adjusted to achieve electroneutrality. During the solving process all cL values (but the second-from-last) will be slightly adjusted. The adjustments are likely negligible experimentally, but this is why cL values in the output table slightly differ from those given for inputs.

Effect of Temperature on LJP

The LJP is temperature dependent. There are two sources of temperature-dependent variation: the Einstein relation and the conductivity table. The former can be easily defined at calculation time, while the latter requires modifying conductances in the ion mobility table. These modifications typically have a small effect on the LJP, so standard temperature (25C) can be assumed for most applications.

The Einstein relation defines diffusion as D = µ * k * T where:

The ion conductivity table is temperature-specific. Ion conductivity was measured experimentally and varies with temperature. The ion conductivity table here assumes standard temperature (25C), but ion conductivity values can be found for many ions at nonstandard temperatures. LJPcalc users desiring to perform LJP calculations at nonstandard temperatures are encouraged to build their own temperature-specific ion tables.

Calculating Ionic Mobility from Charge and Conductivity

Ionic mobility is µ = Λ / (N * e² * |z|) where:

How to Correct for LJP in Electrophysiology Experiments

Patch-clamp electrophysiologists impale cells with glass microelectrodes to measure or clamp their voltage. Amplifier offset voltage is adjusted to achieve a reading of zero volts when the pipette is in open-tip configuration with the bath, but this voltage includes offset for a liquid junction potential (LJP) caused by the free exchange of ions with different mobilities between the pipette and bath solutions. Whole-cell patch-clamp experiments typically fill the pipette with large anions like gluconate, aspartate, or methanesulfonate, and their low mobility (relative to ions like K, Na, and Cl) causes them to accumulate in the pipette and produce a LJP (where the bath is more positive than then pipette). After establishment of whole-cell configuration, ions no longer freely move between pipette and bath solutions (they are separated by the cell membrane), so there is effectively no LJP but the offset voltage is still offsetting as if LJP were present. By knowing the LJP, the scientist can adjust offset voltage to compensate for it, resulting in more accurate measured and clamped voltages.

Vmeter = Vcell + LJP

To correct for LJP, the electrophysiologist must calculate LJP mathematically (using software like LJPcalc) or estimate it experimentally (see the section on this topic below). Once the LJP is known it can be compensated for experimentally to improve accuracy of recorded and clamped voltages.

Vcell = Vmeter - LJP

⚠️ This method assumes that the amplifier voltage was zeroed at the start of the experiment when the pipette was in open-tip configuration with the bath, and that concentration of chloride (if using Ag/AgCl electrodes) in the internal and bath solutions are stable throughout experiments.

Example Patch-Clamp LJP Calculation & Correction

This ion set came from in Figl et al., 2003 Page 8. They have been loaded into LJPcalc such that the pipette solution is c0 and the bath solution is cL. Note that the order of ions has been adjusted to place the most abundant two ions at the bottom. This is ideal for LJPcalc's analytical method.

Name Charge pipette (mM) bath (mM)
K +1 145 2.8
Na +1 13 145
Mg +2 1 2
Ca +2 0 1
HEPES -1 5 5
Gluconate -1 145 0
Cl -1 10 148.8

Loading this table into LJPcalc produces the following output:

Values for cL were adjusted to achieve electro-neutrality:

 Name               | Charge | Conductivity (E-4) | C0 (mM)      | CL (mM)      
--------------------|--------|--------------------|--------------|--------------
 K                  | +1     | 73.5               | 145          | 2.8098265   
 Na                 | +1     | 50.11              | 13           | 144.9794365 
 Mg                 | +2     | 53.06              | 1            | 1.9998212   
 Ca                 | +2     | 59.5               | 0            | 0.9999109   
 HEPES              | -1     | 22.05              | 5            | 4.9990023   
 Gluconate          | -1     | 24.255             | 145          | 0           
 Cl                 | -1     | 76.31              | 10           | 148.789725

Equations were solved in 88.91 ms
LJP at 20 C (293.15 K) = 16.052319631180264 mV

💡 Figl et al., 2003 Page 8 calculated a LJP of 15.6 mV for this ion set (720 µV lesser magnitude than our calculated LJP). As discussed above, differences in ion mobility table values and use of the Nernst-Planck vs. Henderson equation can cause commercial software to report values slightly different than LJPcalc. Experimentally these small differences are negligible, but values produced by LJPcalc are assumed to be more accurate. See Marino et al., 2014 for discussion.

If we have patch-clamp data that indicates a neuron rests at -48.13 mV, what is its true resting potential? Now that we know the LJP, we can subtract it from our measurement:

Vcell = Vmeasured - LJP

Vcell = -48.13 - 16.05 mV

Vcell = -64.18 mV

We now know our cell rests at -64.18 mV.

Zeroed Voltage = LJP + Two Electrode Half-Cell Potentials

The patch-clamp amplifier is typically zeroed at the start of every experiment when the patch pipette is in open-tip configuration with the bath solution. An offset voltage (Voffset) is applied such that the Vmeasured is zero. This process incorporates 3 potentials into the offset voltage:

  • liquid junction potential (LJP) between the pipette solution and the bath solution (mostly from small mobile ions)
  • half-cell potential (HCP) between the reference electrode and the bath solution (mostly from Cl)
  • half-cell potential (HCP) between the recording electrode and the pipette solution (mostly from Cl)

When the amplifier is zeroed before to experiments, all 3 voltages are incorporated into the offset voltage. Since the LJP is the only one that changes after forming whole-cell configuration with a patched cell (it is eliminated), it is the only one that needs to be known and compensated for to achieve a true zero offset (the rest remain constant).

However, if the [Cl] of the internal or bath solutions change during the course of an experiment (most likely to occur when an Ag/AgCl pellet is immersed in a flowing bath solution), the half-cell potentials become significant and affect Vmeasured as they change. This is why agar bridge references are preferred over Ag/AgCl pellets. See Figl et al., 2003 for more information about LJPs as they relate to electrophysiological experiments.

Measuring LJP Experimentally

It is possible to measure LJP experimentally, but this technique is often discouraged because issues with KCl reference electrodes make it difficult to accurately measure (Barry and Diamond, 1970). However, experimental measurement may be the only option to calculate LJP for solutions containing ions with unknown mobilities.

To measure LJP Experimentally:
Step 1: Zero the amplifier with intracellular solution in the bath and in your pipette
Step 2: Replace the bath with extracellular solution
Step 3: The measured voltage is the negative LJP (invert its sign to get LJP)

✔️ Confirm no drift is present by replacing the bath with intracellular solution after step 3 to verify the reading is 0. If it is not 0, some type of drift is occurring and the measured LJP is not accurate.

❓ Why invert the sign of the LJP? The LJP measured in step 2 is the LJP of the pipette relative to the bath, but in electrophysiology experiments convention is to refer to LJP as that of the bath relative to the pipette. LJPs for experiments using typical ACSF bath and physiological K-gluconate pipette solutions are usually near +15 mV.

⚠️ Do not measure LJP using a Ag/AgCl reference electrode! because mobility will be low when the bath is filled with intracellular solution (physiological intracellular solutions have low [Cl]). Use a 3M KCl reference electrode instead, allowing high [K] mobility in intracellular solution and high [Cl] mobility in extracellular solution.

References

  • Marino et al. (2014) - describes a computational method to calculate LJP according to the stationary Nernst-Planck equation. The JAVA software described in this manuscript is open-source and now on GitHub (JLJP). Figure 1 directly compares LJP calculated by the Nernst-Planck vs. Henderson equation.

  • Perram and Stiles (2006) - A review of several methods used to calculate liquid junction potential. This manuscript provides excellent context for the history of LJP calculations and describes the advantages and limitations of each.

  • Shinagawa (1980) "Invalidity of the Henderson diffusion equation shown by the exact solution of the Nernst-Planck equations" - a manuscript which argues that the Henderson equation is inferior to solved Nernst-Planck-Poisson equations due to how it accounts for ion flux in the charged diffusion zone.

  • Lin (2011) "The Poisson The Poisson-Nernst-Planck (PNP) system for ion transport (PNP) system for ion transport" - a PowerPoint presentation which reviews mathematical methods to calculate LJP with notes related to its application in measuring voltage across cell membranes.

  • Nernst-Planck equation (Wikipedia)

  • Goldman Equation (Wikipedia)

  • EGTA charge and pH - Empirical determination of EGTA charge state distribution as a function of pH.

  • LJPCalcWin - A Program for Calculating Liquid Junction Potentials

  • LJP Corrections (Axon Instruments Application Note) describes how to calculate LJP using ClampEx and LJPCalcWin and also summarizes how to measure LJP experimentally

  • LJP Corrections (Figl et al., AxoBits 39) summarizes LJP and discusses measurement and calculation with ClampEx

Markdown source code last modified on May 25th, 2022
---
title: LJP Theory and Correction
description: What causes Liquid Junction Potential (LJP) and how to correct for it in patch-clamp experiments
date: 2021-01-13
tags: science
---

# Liquid Junction Potential (LJP) Theory and Correction

This page contains notes about the analytical methods [LJPcalc](https://swharden.com/LJPcalc/) uses to calculate LJP from ion tables
 as well as notes for experimenters about what causes LJP and how to compensate for it in electrophysiology experiments.

> 💡 **Use [LJPcalc](https://swharden.com/LJPcalc/) to calculate Liquid Junction Potential (LJP) in your browser**

## Table of Contents

![](TOC)

## LJP Calculation Notes

### LJPcalc Calculation Method

LJPcalc calculates the liquid junction potential according to the stationary Nernst-Planck equation which is typically regarded as superior to the simpler Henderson equation used by most commercial LJP calculators. Both equations produce nearly identical LJPs, but the Henderson equation becomes inaccurate as ion concentrations increase, and also when calculating LJP for solutions containing polyvalent ions.

### LJPcalc Ion Mobility Library

LJPcalc uses an extensive [ion mobility library](../iontable)

The ion mobility table is stored in Markdown format. Not only does Markdown make it easy to display the table nicely in a browser, 
but it also makes the table easy to edit in any text editor. Users desiring to use their own ion mobilities or add new ions to the table
can do so by editing the `IonTable.md` file adjacent to `LJPcalc.exe` as needed.

### Influence of Ion Sequence on Calculated LJP

> 💡 LJPcalc automatically sorts the ion table into an ideal sequence prior to solving for LJP. Attention only needs to be paid to the ion sequence if automatic sorting is disabled.

When calculating LJP for a set of ions it is important to consider the sequence in which they are listed. Additional information can be found in [Marino et al., 2014](https://arxiv.org/abs/1403.3640) which describes the exact computational methods employed by LJPcalc.

* **The last ion's c0 may be overridden** to achieve electroneutrality on the c0 side. This will not occur if the sum of charge on the c0 side is zero.

* **cL for most ions will be slightly adjusted** to achieve electroneutrality on the cL side. The second-to-last ion's cL (which cannot equal its c0) will remain fixed, while the last cL will be adjusted to achieve electroneutrality. During the solving process all cL values (but the second-from-last) will be slightly adjusted. The adjustments are likely negligible experimentally, but this is why cL values in the output table slightly differ from those given for inputs.

### Effect of Temperature on LJP

**The LJP is temperature dependent.** There are two sources of temperature-dependent variation: the Einstein relation and the conductivity table. The former can be easily defined at calculation time, while the latter requires modifying conductances in the ion mobility table. These modifications typically have a small effect on the LJP, so standard temperature (25C) can be assumed for most applications.

**The [Einstein relation](https://en.wikipedia.org/wiki/Einstein_relation_(kinetic_theory))** defines diffusion as **`D = µ * k * T`** where:

* **`D`** is the diffusion coefficient
* **`µ`** (mu) is [ionic mobility](https://en.wikipedia.org/wiki/Electrical_mobility)
* **`k`** is the [Boltzmann constant](https://en.wikipedia.org/wiki/Boltzmann_constant) (1.380649e-23 J / K)
* **`T`** is temperature (K)

**The ion conductivity table is temperature-specific.** Ion conductivity was measured experimentally and varies with temperature. The ion conductivity table here assumes standard temperature (25C), but ion conductivity values can be found for many ions at nonstandard temperatures. LJPcalc users desiring to perform LJP calculations at nonstandard temperatures are encouraged to build their own temperature-specific ion tables.

### Calculating Ionic Mobility from Charge and Conductivity

Ionic mobility is **`µ = Λ / (N * e² * |z|)`** where:

* **`µ`** (mu) is [ionic mobility](https://en.wikipedia.org/wiki/Electrical_mobility) (m² / V / sec)
* **`Λ`** (Lambda) is [molar conductivity](https://en.wikipedia.org/wiki/Molar_conductivity) (S * cm²/ mol)
* **`N`** is the [Avogadro constant](https://en.wikipedia.org/wiki/Avogadro_constant) (6.02214076e23 particles / mol)
* **`e`** is the [elementary charge](https://en.wikipedia.org/wiki/Elementary_charge) (1.602176634e-19 Coulombs)
* **`z`** is the absolute value of the [elementary charge](https://en.wikipedia.org/wiki/Elementary_charge) of the ion

## How to Correct for LJP in Electrophysiology Experiments

Patch-clamp electrophysiologists impale cells with glass microelectrodes to measure or clamp their voltage.
Amplifier offset voltage is adjusted to achieve a reading of zero volts when the pipette is in open-tip configuration with the bath,
but this voltage includes offset for a _liquid junction potential_ (LJP) caused by the free exchange of ions with different mobilities between the pipette and bath solutions.
Whole-cell patch-clamp experiments typically fill the pipette with large anions like gluconate, aspartate, or methanesulfonate, and their low mobility 
(relative to ions like K, Na, and Cl) causes them to accumulate in the pipette and produce a LJP (where the bath is more positive than then pipette).
After establishment of whole-cell configuration, ions no longer freely move between pipette and bath solutions (they are separated by the cell membrane), so
there is effectively no LJP but the offset voltage is still offsetting as if LJP were present. By knowing the LJP, the scientist can
adjust offset voltage to compensate for it, resulting in more accurate measured and clamped voltages.

<p align="center">V<sub>meter</sub> = V<sub>cell</sub> + LJP</p>

**To correct for LJP,** the electrophysiologist must calculate LJP mathematically (using software like LJPcalc) or estimate it experimentally (see the section on this topic below). Once the LJP is known it can be compensated for experimentally to improve accuracy of recorded and clamped voltages.

<p align="center">V<sub>cell</sub> = V<sub>meter</sub> - LJP</p>

> ⚠️ This method assumes that the amplifier voltage was zeroed at the start of the experiment when the pipette was in open-tip configuration 
with the bath, and that concentration of chloride (if using Ag/AgCl electrodes) in the internal and bath solutions are stable throughout experiments.

<div align="center">

![](ljp.png)

</div>

### Example Patch-Clamp LJP Calculation & Correction

This ion set came from in [Figl et al., 2003](https://medicalsciences.med.unsw.edu.au/sites/default/files/soms/page/ElectroPhysSW/AxoBits39New.pdf) Page 8. They have been loaded into LJPcalc such that the pipette solution is c0 and the bath solution is cL. Note that the order of ions has been adjusted to place the most abundant two ions at the bottom. This is ideal for LJPcalc's analytical method.


 Name       | Charge | pipette (mM) | bath (mM)      
------------|--------|--------------|---------
 K          | +1     | 145          | 2.8
 Na         | +1     | 13           | 145
 Mg         | +2     | 1            | 2
 Ca         | +2     | 0            | 1
 HEPES      | -1     | 5            | 5
 Gluconate  | -1     | 145          | 0           
 Cl         | -1     | 10           | 148.8


Loading this table into LJPcalc produces the following output:

```
Values for cL were adjusted to achieve electro-neutrality:

 Name               | Charge | Conductivity (E-4) | C0 (mM)      | CL (mM)      
--------------------|--------|--------------------|--------------|--------------
 K                  | +1     | 73.5               | 145          | 2.8098265   
 Na                 | +1     | 50.11              | 13           | 144.9794365 
 Mg                 | +2     | 53.06              | 1            | 1.9998212   
 Ca                 | +2     | 59.5               | 0            | 0.9999109   
 HEPES              | -1     | 22.05              | 5            | 4.9990023   
 Gluconate          | -1     | 24.255             | 145          | 0           
 Cl                 | -1     | 76.31              | 10           | 148.789725

Equations were solved in 88.91 ms
LJP at 20 C (293.15 K) = 16.052319631180264 mV
```

> 💡 _[Figl et al., 2003](https://medicalsciences.med.unsw.edu.au/sites/default/files/soms/page/ElectroPhysSW/AxoBits39New.pdf) Page 8 calculated a LJP of 15.6 mV for this ion set (720 µV lesser magnitude than our calculated LJP). As discussed above, differences in ion mobility table values and use of the Nernst-Planck vs. Henderson equation can cause commercial software to report values slightly different than LJPcalc. Experimentally these small differences are negligible, but values produced by LJPcalc are assumed to be more accurate. See [Marino et al., 2014](https://arxiv.org/abs/1403.3640) for discussion._

If we have patch-clamp data that indicates a neuron rests at -48.13 mV, what is its true resting potential? Now that we know the LJP, we can subtract it from our measurement:

<p align="center">V<sub>cell</sub> = V<sub>measured</sub> - LJP</p>

<p align="center">V<sub>cell</sub> = -48.13 - 16.05 mV</p>

<p align="center">V<sub>cell</sub> = -64.18 mV</p>

We now know our cell rests at -64.18 mV.

### Zeroed Voltage = LJP + Two Electrode Half-Cell Potentials

The patch-clamp amplifier is typically zeroed at the start of every experiment when the patch pipette is in open-tip configuration 
with the bath solution. An offset voltage (V<sub>offset</sub>) is applied such that the V<sub>measured</sub> is zero. 
This process incorporates 3 potentials into the offset voltage:

* **liquid junction potential (LJP)** between the **pipette** solution and the **bath** solution (mostly from small mobile ions)
* **half-cell potential (HCP)** between the **reference electrode** and the **bath** solution (mostly from Cl)
* **half-cell potential (HCP)** between the **recording electrode** and the **pipette** solution (mostly from Cl)

When the amplifier is zeroed before to experiments, all 3 voltages are incorporated into the offset voltage. 
Since the LJP is the only one that changes after forming whole-cell configuration with a patched cell (it is eliminated),
it is the only one that needs to be known and compensated for to achieve a true zero offset (the rest remain constant).

However, if the [Cl] of the internal or bath solutions change during the course of an experiment 
(most likely to occur when an Ag/AgCl pellet is immersed in a flowing bath solution), 
the half-cell potentials become significant and affect V<sub>measured</sub> as they change. 
This is why agar bridge references are preferred over Ag/AgCl pellets.
See [Figl et al., 2003](https://medicalsciences.med.unsw.edu.au/sites/default/files/soms/page/ElectroPhysSW/AxoBits39New.pdf) 
for more information about LJPs as they relate to electrophysiological experiments.

### Measuring LJP Experimentally

It is possible to measure LJP experimentally, but this technique is often discouraged because issues with KCl reference electrodes make it difficult 
to accurately measure ([Barry and Diamond, 1970](https://link.springer.com/article/10.1007/BF01868010)). However, experimental measurement 
may be the only option to calculate LJP for solutions containing ions with unknown mobilities.

**To measure LJP Experimentally:**<br>
&bull; **Step 1:** Zero the amplifier with intracellular solution in the bath and in your pipette<br>
&bull; **Step 2:** Replace the bath with extracellular solution<br>
&bull; **Step 3:** The measured voltage is the negative LJP (invert its sign to get LJP)

> **✔️ Confirm no drift is present** by replacing the bath with intracellular solution after step 3 to verify the reading is 0. 
If it is not 0, some type of drift is occurring and the measured LJP is not accurate.

> **❓ Why invert the sign of the LJP?** The LJP measured in step 2 is the LJP of the _pipette_ relative to 
the _bath_, but in electrophysiology experiments convention is to refer to LJP as that of the _bath_ relative to the _pipette_. 
LJPs for experiments using typical ACSF bath and physiological K-gluconate pipette solutions are usually near +15 mV.

> **⚠️ Do not measure LJP using a Ag/AgCl reference electrode!** because mobility will be low when the bath is filled with intracellular solution (physiological intracellular solutions have low [Cl]). 
Use a 3M KCl reference electrode instead, allowing high [K] mobility in intracellular solution and high [Cl] mobility in extracellular solution.

## References

* **[Marino et al. (2014)](https://arxiv.org/abs/1403.3640)** - describes a computational method to calculate LJP according to the stationary Nernst-Planck equation. The JAVA software described in this manuscript is open-source and now on GitHub ([JLJP](https://github.com/swharden/jljp)). Figure 1 directly compares LJP calculated by the Nernst-Planck vs. Henderson equation.

* **[Perram and Stiles (2006)](https://pubs.rsc.org/en/content/articlelanding/2006/cp/b601668e)** - A review of several methods used to calculate liquid junction potential. This manuscript provides excellent context for the history of LJP calculations and describes the advantages and limitations of each.

* **[Shinagawa (1980)](https://www.ncbi.nlm.nih.gov/pubmed/7401663)** _"Invalidity of the Henderson diffusion equation shown by the exact solution of the Nernst-Planck equations"_ - a manuscript which argues that the Henderson equation is inferior to solved Nernst-Planck-Poisson equations due to how it accounts for ion flux in the charged diffusion zone.

* **[Lin (2011)](http://www.sci.osaka-cu.ac.jp/~ohnita/2010/TCLin.pdf)** _"The Poisson The Poisson-Nernst-Planck (PNP) system for ion transport (PNP) system for ion transport"_ - a PowerPoint presentation which reviews mathematical methods to calculate LJP with notes related to its application in measuring voltage across cell membranes.

* **[Nernst-Planck equation](https://en.wikipedia.org/wiki/Nernst%E2%80%93Planck_equation)** (Wikipedia)

* **[Goldman Equation](https://en.wikipedia.org/wiki/Goldman_equation)** (Wikipedia)

* **[EGTA charge and pH](https://www.sciencedirect.com/science/article/pii/S0165027099000369?via%3Dihub#FIG1)** - Empirical determination of EGTA charge state distribution as a function of pH.

* **[LJPCalcWin](https://medicalsciences.med.unsw.edu.au/sites/default/files/soms/page/ElectroPhysSW/JPCalcWin-Demo%20Manual.pdf)** - A Program for Calculating Liquid Junction Potentials

* **[LJP Corrections](http://beenhakkerlab.org/lab-links/ephys/Guides/Theory/Liquid-Junction-Potential-Corrections-Axon.pdf)** (Axon Instruments Application Note) describes how to calculate LJP using ClampEx and LJPCalcWin and also summarizes how to measure LJP experimentally

* **[LJP Corrections](https://medicalsciences.med.unsw.edu.au/sites/default/files/soms/page/ElectroPhysSW/AxoBits39New.pdf)** (Figl et al., AxoBits 39) summarizes LJP and discusses measurement and calculation with ClampEx
January 12th, 2021

Google Charts in Blazor

Google Charts is a free interactive JavaScript charting framework. The Google Chart Gallery showcases many of the available chart types and options. This project shows how to create data from C# functions in Blazor, then use JavaScript interop to pass arrays into JavaScript for display with Google Charts.

index.razor

This page contains a div where the chart will be displayed, controls for customizing settings, and a few functions in the code-behind to call a JavaScript function that updates the chart.

@page "/"
@inject IJSRuntime JsRuntime

<!-- this is where the Google Chart is displayed -->
<div id="chart_div" style="height: 400px;"></div>

<!-- buttons call C# functions -->
<button @onclick="PlotSin">Sin</button>
<button @onclick="PlotRandom">Random</button>
<button @onclick="PlotWalk">Walk</button>
<button @onclick="PlotRandomXY">RandomXY</button>

<!-- a slider bound to a C# field -->
<input type="range" @bind="PointCount" @bind:event="oninput">
@code{
    private int PointCount = 123;
    Random Rand = new Random();

    private void PlotData(double[] xs, double[] ys)
    {
        // This function calls a JavaScript function to update the chart.
        // Notice how multiple parameters are passed in.
        JsRuntime.InvokeVoidAsync("createNewChart", new { xs, ys });
    }

    private void PlotSin()
    {
        double[] xs = Enumerable.Range(0, PointCount).Select(x => (double)x).ToArray();
        double[] ys = xs.Select(x => Math.Sin(x / 10)).ToArray();
        PlotData(xs, ys);
    }

    private void PlotRandom()
    {
        double[] xs = Enumerable.Range(0, PointCount).Select(x => (double)x).ToArray();
        double[] ys = xs.Select(x => (Rand.NextDouble() - .5) * 1000).ToArray();
        PlotData(xs, ys);
    }

    private void PlotWalk()
    {
        double[] xs = Enumerable.Range(0, PointCount).Select(x => (double)x).ToArray();
        double[] ys = new double[PointCount];
        for (int i = 1; i < ys.Length; i++)
            ys[i] = ys[i - 1] + Rand.NextDouble() - .5;
        PlotData(xs, ys);
    }

    private void PlotRandomXY()
    {
        double[] xs = Enumerable.Range(0, PointCount).Select(x => Rand.NextDouble()).ToArray();
        double[] ys = Enumerable.Range(0, PointCount).Select(x => Rand.NextDouble()).ToArray();
        PlotData(xs, ys);
    }
}

index.html

These JavaScript blocks were added just before the closing </body> tag

<script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script>

<script>
    google.charts.load('current', { packages: ['corechart', 'line'] });

    // draw an empty chart when the page first loads
    google.charts.setOnLoadCallback(initChart);
    function initChart() {
        var xs = [];
        var ys = [];
        window.createNewChart({xs, ys});
    }

    // draw a new chart given X/Y values
    window.createNewChart = (params) => {
        var xs = params.xs;
        var ys = params.ys;

        var data = new google.visualization.DataTable();
        data.addColumn('number', 'X');
        data.addColumn('number', 'Y');

        for (var i = 0; i < ys.length; i++) {
            data.addRow([xs[i], ys[i]]);
        }

        var options = {
            hAxis: { title: 'Horizontal Axis Label' },
            vAxis: { title: 'Vertical Axis Label' },
            title: 'This is a Google Chart in Blazor',
            legend: { position: 'none' },
        };

        var chart = new google.visualization.LineChart(document.getElementById('chart_div'));

        chart.draw(data, options);
    };

</script>

Automatic Resizing

Plots look bad when the window is resized because Google Charts adopts a fixed size on each render. To give the appearance of fluid charts (that resize to fit their container as its size changes), add some JavaScript to re-render on every resize.

window.onresize = function () { initChart(); };

We can't call our existing createNewChart() method because that expects data (from C#/Blazor) passed-in as a parameter. To support this type of resizing, the call from C# must be modified to store data arrays at the window level so they can be later accessed when the chart is plotted again. This would take some re-structuring of this project, but it's possible.

Conclusions

  • Google Charts is a straightforward way to display data in mouse-interactive graphs on the web.

  • JS interop allows Blazor to pass data to a JavaScript function that can plot it on a Google Chart.

  • Extra steps are required to automatically resize a Google Chart when its container size changes.

  • For interactive graphs in desktop applications, check out ScottPlot (an open-source plotting library for .NET that makes it easy to interactively display large datasets).

Resources

Markdown source code last modified on January 18th, 2021
---
title: Google Charts in Blazor
date: 2021-01-12 19:15:00
tags: blazor, csharp
---

# Google Charts in Blazor

**Google Charts is a free interactive JavaScript charting framework.** The [Google Chart Gallery](https://developers.google.com/chart/interactive/docs/gallery) showcases many of the available chart types and options. This project shows how to create data from C# functions in Blazor, then use JavaScript interop to pass arrays into JavaScript for display with Google Charts.

<div class="text-center">

[![](blazor-google-charts.jpg)](app)

</div>

* [Google Charts with Blazor Demo](app) 👈 Try it live!

* Source code: [blazor-google-charts.zip](blazor-google-charts.zip)

## index.razor

This page contains a `div` where the chart will be displayed, controls for customizing settings, and a few functions in the code-behind to call a JavaScript function that updates the chart.

```html
@page "/"
@inject IJSRuntime JsRuntime

<!-- this is where the Google Chart is displayed -->
<div id="chart_div" style="height: 400px;"></div>

<!-- buttons call C# functions -->
<button @onclick="PlotSin">Sin</button>
<button @onclick="PlotRandom">Random</button>
<button @onclick="PlotWalk">Walk</button>
<button @onclick="PlotRandomXY">RandomXY</button>

<!-- a slider bound to a C# field -->
<input type="range" @bind="PointCount" @bind:event="oninput">
```

```cs
@code{
    private int PointCount = 123;
    Random Rand = new Random();

    private void PlotData(double[] xs, double[] ys)
    {
        // This function calls a JavaScript function to update the chart.
		// Notice how multiple parameters are passed in.
        JsRuntime.InvokeVoidAsync("createNewChart", new { xs, ys });
    }

    private void PlotSin()
    {
        double[] xs = Enumerable.Range(0, PointCount).Select(x => (double)x).ToArray();
        double[] ys = xs.Select(x => Math.Sin(x / 10)).ToArray();
        PlotData(xs, ys);
    }

    private void PlotRandom()
    {
        double[] xs = Enumerable.Range(0, PointCount).Select(x => (double)x).ToArray();
        double[] ys = xs.Select(x => (Rand.NextDouble() - .5) * 1000).ToArray();
        PlotData(xs, ys);
    }

    private void PlotWalk()
    {
        double[] xs = Enumerable.Range(0, PointCount).Select(x => (double)x).ToArray();
        double[] ys = new double[PointCount];
        for (int i = 1; i < ys.Length; i++)
            ys[i] = ys[i - 1] + Rand.NextDouble() - .5;
        PlotData(xs, ys);
    }

    private void PlotRandomXY()
    {
        double[] xs = Enumerable.Range(0, PointCount).Select(x => Rand.NextDouble()).ToArray();
        double[] ys = Enumerable.Range(0, PointCount).Select(x => Rand.NextDouble()).ToArray();
        PlotData(xs, ys);
    }
}
```

## index.html

These JavaScript blocks were added just before the closing `</body>` tag

```js
<script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script>

<script>
    google.charts.load('current', { packages: ['corechart', 'line'] });

    // draw an empty chart when the page first loads
    google.charts.setOnLoadCallback(initChart);
    function initChart() {
        var xs = [];
        var ys = [];
        window.createNewChart({xs, ys});
    }

    // draw a new chart given X/Y values
    window.createNewChart = (params) => {
        var xs = params.xs;
        var ys = params.ys;

        var data = new google.visualization.DataTable();
        data.addColumn('number', 'X');
        data.addColumn('number', 'Y');

        for (var i = 0; i < ys.length; i++) {
            data.addRow([xs[i], ys[i]]);
        }

        var options = {
            hAxis: { title: 'Horizontal Axis Label' },
            vAxis: { title: 'Vertical Axis Label' },
            title: 'This is a Google Chart in Blazor',
            legend: { position: 'none' },
        };

        var chart = new google.visualization.LineChart(document.getElementById('chart_div'));

        chart.draw(data, options);
    };

</script>
```

## Automatic Resizing

Plots look bad when the window is resized because Google Charts adopts a fixed size on each render. To give the appearance of fluid charts (that resize to fit their container as its size changes), add some JavaScript to re-render on every resize.

```js
window.onresize = function () { initChart(); };
```

We can't call our existing `createNewChart()` method because that expects data (from C#/Blazor) passed-in as a parameter. To support this type of resizing, the call from C# must be modified to _store_ data arrays at the window level so they can be later accessed when the chart is plotted again. This would take some re-structuring of this project, but it's possible.

## Conclusions

* Google Charts is a straightforward way to display data in mouse-interactive graphs on the web. 

* JS interop allows Blazor to pass data to a JavaScript function that can plot it on a Google Chart.

* Extra steps are required to automatically resize a Google Chart when its container size changes.

* For interactive graphs in desktop applications, check out [ScottPlot](https://swharden.com/scottplot) (an open-source plotting library for .NET that makes it easy to interactively display large datasets).

## Resources

* Live demo: [Google Charts with Blazor Demo](app)

* Source code: [blazor-google-charts.zip](blazor-google-charts.zip)

* Source code on GitHub: [/examples/2021-01-10-blazor-google-charts](https://github.com/swharden/Csharp-Data-Visualization/tree/master/examples/2021-01-10-blazor-google-charts)

* [C# Data Visualization](https://github.com/swharden/Csharp-Data-Visualization) on GitHub
Pages