From c3cda754e2925b422ce0834bef461eb7f3f41447 Mon Sep 17 00:00:00 2001 From: bbm Date: Fri, 27 Feb 2026 16:28:18 -0500 Subject: [PATCH] adding tools for generating and deploying scattering table --- deploy_scattering_table.sh | 5 + util/scattering_table_html.py | 179 ++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 deploy_scattering_table.sh create mode 100644 util/scattering_table_html.py diff --git a/deploy_scattering_table.sh b/deploy_scattering_table.sh new file mode 100644 index 0000000..9b76ecc --- /dev/null +++ b/deploy_scattering_table.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +TARGET_DIR=${TARGET_DIR:-/var/www/html/resources/n-lengths} + +python -m util.scattering_table_html $TARGET_DIR diff --git a/util/scattering_table_html.py b/util/scattering_table_html.py new file mode 100644 index 0000000..18dd268 --- /dev/null +++ b/util/scattering_table_html.py @@ -0,0 +1,179 @@ +from pathlib import Path + +import numpy as np +from periodictable.core import default_table, PeriodicTable +from periodictable.nsf import Neutron + +# TODO: add a citation column to the html scattering table +def scattering_table_html(path: Path|str|None=None, table: PeriodicTable|None=None) -> str: + """ + Generate an html table, returning it as a string. If path is given, write the + html to that path. + + Note: requires the uncertainties package, which is not otherwise required by periodictable. + """ + from uncertainties import ufloat as U # type: ignore[import-untyped] + + head = """\ + + + Neutron Cross Sections + +""" + table = default_table(table) + + def format_num(re, re_unc, im=None, im_unc=None): + # Note: using NaN for uncertainty for derived values that should be + # left blank in the table (currently 191,193Ir cross sections) + re_str = "" if re is None or np.isnan(re_unc) else f"{U(re, re_unc):fS}" if re_unc else str(re) if re else "0" + if im is not None: + im_value = f"{U(abs(im), im_unc):fS}" if im_unc else str(abs(im)) + im_str = f"
{'+' if im >= 0 else '–'} {im_value}j" + else: + im_str = "" + return f"{re_str}{im_str}" + + rows = [] + rows.append(f""" + + + Z + A + I(π) + abundance % + bc {Neutron.b_c_units} + b+ {Neutron.bp_units} + b {Neutron.bm_units} + σc {Neutron.coherent_units} + σi {Neutron.incoherent_units} + σs {Neutron.total_units} + σa {Neutron.absorption_units} + """) + + # Generate table rows + for el in [table.n, *table]: + element_number = el.number + isotopes = [iso for iso in el if iso.neutron.absorption is not None or iso.abundance] + singleton = len(isotopes) == 1 + symbol = el.symbol + row_id = symbol if symbol != "n" else "" # Special case for bare neutron + # print(f"{el=} {A=} {singleton=} {el.neutron.has_sld()=} {[*el]}") + if element_number <= 96 and not singleton: + # Multiple isotopes: put element summary above + n = el.neutron + rows.append(f""" + + {symbol} + {element_number} + + + + {format_num(n.b_c, n.b_c_unc, n.b_c_i, n.b_c_i_unc)} + {format_num(n.bp, n.bp_unc, n.bp_i, n.bp_i_unc)} + {format_num(n.bm, n.bm_unc, n.bm_i, n.bm_i_unc)} + {format_num(n.coherent, n.coherent_unc)} + {format_num(n.incoherent, n.incoherent_unc)} + {format_num(n.total, n.total_unc)} + {format_num(n.absorption, n.absorption_unc)} + """) + + for iso in isotopes: + isotope_number = iso.isotope + spin = getattr(iso, "nuclear_spin", "") + n = iso.neutron + abundance = ( + f"{U(iso.abundance, iso._abundance_unc):fS}" if iso._abundance_unc + else "100" if iso.abundance == 100.0 + else "" if iso.abundance == 0.0 + else f"{iso.abundance}" + ) + rows.append(f""" + + {symbol if singleton else ''} + {element_number if singleton else ''} + {isotope_number} + {spin} + {abundance} + {format_num(n.b_c, n.b_c_unc, n.b_c_i, n.b_c_i_unc)} + {format_num(n.bp, n.bp_unc, n.bp_i, n.bp_i_unc)} + {format_num(n.bm, n.bm_unc, n.bm_i, n.bm_i_unc)} + {format_num(n.coherent, n.coherent_unc)} + {format_num(n.incoherent, n.incoherent_unc)} + {format_num(n.total, n.total_unc)} + {format_num(n.absorption, n.absorption_unc)} + """) + + # Note: don't need \n between rows since we add it to each. + formatted_table = ''.join(rows) + html = f""" + + +{head} + +

Scattering lengths and cross sections for various isotopes evaluated at 2200 m s–1 +

+ +{formatted_table} +
+

This table has been compiled from various sources for the user's convenience and does not represent a critical evaluation by the NIST Center for Neutron Research. +See python-periodictable on github for a list of citations.

+

Natural abundance is from IUPAC Commission on Isotopic Abundances and Atomic Weights (CIAAW)

+ + +""" + + if path: + # Save the HTML to a file + with open(path, 'w', encoding='utf-8') as f: + f.write(html) + + print(f"HTML table saved to {str(path)}") + + return html + +if __name__ == "__main__": + import sys + # Example usage: generate the HTML table and save it to a file + output_dir = sys.argv[1] if len(sys.argv) > 1 else "." + output_filename = sys.argv[2] if len(sys.argv) > 2 else "scattering_table.html" + output_path = Path(output_dir) / output_filename + scattering_table_html(path=output_path) \ No newline at end of file