From a524e3015ce20c33f1ef56f09b0a8e4e5abb517e Mon Sep 17 00:00:00 2001 From: StephenNneji <34302892+StephenNneji@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:43:38 +0000 Subject: [PATCH 1/9] Fixes matlab engine not shutting down (#196) --- ratapi/wrappers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ratapi/wrappers.py b/ratapi/wrappers.py index 74eda41e..9475b93e 100644 --- a/ratapi/wrappers.py +++ b/ratapi/wrappers.py @@ -23,9 +23,12 @@ def start_matlab(): future = None if os.environ.get("DELAY_MATLAB_START", "0") == "0": with suppress(ImportError): + import atexit + import matlab.engine future = matlab.engine.start_matlab(background=True) + atexit.register(lambda: future.result()) return future From 048c4e23f6950b507f8b1dd6d67de97dcf6ed9f0 Mon Sep 17 00:00:00 2001 From: StephenNneji <34302892+StephenNneji@users.noreply.github.com> Date: Mon, 9 Feb 2026 13:08:47 +0000 Subject: [PATCH 2/9] Unifies the contrast number used by python and cpp to be same with matlab and Data points now show when `show_error` is False (#197) --- cpp/RAT | 2 +- .../absorption/volume_thiol_bilayer.py | 23 +- .../bayes_benchmark/bayes_benchmark.ipynb | 11 +- .../convert_rascal_project/Model_IIb.py | 7 +- ratapi/examples/domains/alloy_domains.py | 7 +- ratapi/examples/domains/domains_XY_model.py | 9 +- ratapi/examples/languages/custom_bilayer.cpp | 10 +- ratapi/examples/languages/custom_bilayer.py | 11 +- .../DSPC_custom_layers.ipynb | 556 +----------------- .../normal_reflectivity/custom_XY_DSPC.py | 5 +- .../custom_bilayer_DSPC.py | 10 +- ratapi/utils/plotting.py | 15 +- ratapi/wrappers.py | 4 +- tests/test_wrappers.py | 4 +- 14 files changed, 88 insertions(+), 586 deletions(-) diff --git a/cpp/RAT b/cpp/RAT index 7535f967..7da24c8d 160000 --- a/cpp/RAT +++ b/cpp/RAT @@ -1 +1 @@ -Subproject commit 7535f96759ee352919cae010f64af79652cbd094 +Subproject commit 7da24c8d9700bff1eb5c660f5389214c473a1c24 diff --git a/ratapi/examples/absorption/volume_thiol_bilayer.py b/ratapi/examples/absorption/volume_thiol_bilayer.py index 444d5cac..2b9f7fd4 100644 --- a/ratapi/examples/absorption/volume_thiol_bilayer.py +++ b/ratapi/examples/absorption/volume_thiol_bilayer.py @@ -23,6 +23,9 @@ def volume_thiol_bilayer(params, bulk_in, bulk_out, contrast): The second output parameter should be the substrate roughness. """ + # Note - The first contrast number is 1 (not 0) so be careful if you use + # this variable for array indexing. + subRough = params[0] alloyThick = params[1] alloySLDUp = params[2] @@ -92,11 +95,11 @@ def volume_thiol_bilayer(params, bulk_in, bulk_out, contrast): # Correct head SLD based on hydration thiolHeadHydr = thiolHeadHydr / 100 - sldHead = sldHead * (1 - thiolHeadHydr) + (thiolHeadHydr * bulk_out[contrast]) + sldHead = sldHead * (1 - thiolHeadHydr) + (thiolHeadHydr * bulk_out[contrast - 1]) # Now correct both the SLDs for the coverage parameter - sldTail = (thiolCoverage * sldTail) + ((1 - thiolCoverage) * bulk_out[contrast]) - sldHead = (thiolCoverage * sldHead) + ((1 - thiolCoverage) * bulk_out[contrast]) + sldTail = (thiolCoverage * sldTail) + ((1 - thiolCoverage) * bulk_out[contrast - 1]) + sldHead = (thiolCoverage * sldHead) + ((1 - thiolCoverage) * bulk_out[contrast - 1]) SAMTAILS = [thickTail, sldTail, 0, goldRough] SAMHEAD = [thickHead, sldHead, 0, goldRough] @@ -113,7 +116,7 @@ def volume_thiol_bilayer(params, bulk_in, bulk_out, contrast): sldHead = sumbHead / vHead thickHead = vHead / bilayerAPM bilHeadHydr = bilHeadHydr / 100 - sldHead = sldHead * (1 - bilHeadHydr) + (bilHeadHydr * bulk_out[contrast]) + sldHead = sldHead * (1 - bilHeadHydr) + (bilHeadHydr * bulk_out[contrast - 1]) sldTail = sumbTail / vTail thickTail = vTail / bilayerAPM @@ -121,9 +124,9 @@ def volume_thiol_bilayer(params, bulk_in, bulk_out, contrast): sldMe = sumbMe / vMe thickMe = vMe / bilayerAPM - sldTail = (bilayerCoverage * sldTail) + ((1 - bilayerCoverage) * bulk_out[contrast]) - sldHead = (bilayerCoverage * sldHead) + ((1 - bilayerCoverage) * bulk_out[contrast]) - sldMe = (bilayerCoverage * sldMe) + ((1 - bilayerCoverage) * bulk_out[contrast]) + sldTail = (bilayerCoverage * sldTail) + ((1 - bilayerCoverage) * bulk_out[contrast - 1]) + sldHead = (bilayerCoverage * sldHead) + ((1 - bilayerCoverage) * bulk_out[contrast - 1]) + sldMe = (bilayerCoverage * sldMe) + ((1 - bilayerCoverage) * bulk_out[contrast - 1]) BILTAILS = [thickTail, sldTail, 0, bilayerRough] BILHEAD = [thickHead, sldHead, 0, bilayerRough] @@ -131,11 +134,11 @@ def volume_thiol_bilayer(params, bulk_in, bulk_out, contrast): BILAYER = [BILHEAD, BILTAILS, BILME, BILME, BILTAILS, BILHEAD] - CW = [cwThick, bulk_out[contrast], 0, bilayerRough] + CW = [cwThick, bulk_out[contrast - 1], 0, bilayerRough] - if contrast == 1 or contrast == 3: + if contrast == 2 or contrast == 4: output = [alloyUp, gold, SAMTAILS, SAMHEAD, CW, *BILAYER] - else: + elif contrast == 1 or contrast == 3: output = [alloyDown, gold, SAMTAILS, SAMHEAD, CW, *BILAYER] return output, subRough diff --git a/ratapi/examples/bayes_benchmark/bayes_benchmark.ipynb b/ratapi/examples/bayes_benchmark/bayes_benchmark.ipynb index 0c083924..bb9bba9f 100644 --- a/ratapi/examples/bayes_benchmark/bayes_benchmark.ipynb +++ b/ratapi/examples/bayes_benchmark/bayes_benchmark.ipynb @@ -188,7 +188,7 @@ "back_param = project.background_parameters[0]\n", "background = np.linspace(back_param.min, back_param.max, 30)\n", "\n", - "controls = RAT.Controls(procedure=\"calculate\", calcSldDuringFit=True, display=\"off\")\n", + "controls = RAT.Controls(procedure=\"calculate\", display=\"off\")\n", "\n", "# function to calculate exp(-chi_squared / 2) for a given pair of roughness/background values\n", "def calculate_posterior(roughness_index: int, background_index: int) -> float:\n", @@ -339,13 +339,6 @@ "fig.tight_layout()\n", "fig.show()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -364,7 +357,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.10.16" } }, "nbformat": 4, diff --git a/ratapi/examples/convert_rascal_project/Model_IIb.py b/ratapi/examples/convert_rascal_project/Model_IIb.py index d7c61526..2e15d5d1 100644 --- a/ratapi/examples/convert_rascal_project/Model_IIb.py +++ b/ratapi/examples/convert_rascal_project/Model_IIb.py @@ -5,6 +5,9 @@ def Model_IIb(params, bulk_in, bulk_out, contrast): """Calculate layer parameters for a monolayer volume model at two deuterations.""" + # Note - The first contrast number is 1 (not 0) so be careful if you use + # this variable for array indexing. Same applies to the domain number. + # converted from matlab file Model_IIb.m Roughness, APM, thickHead, theta = params @@ -49,7 +52,7 @@ def Model_IIb(params, bulk_in, bulk_out, contrast): vTail = 2 * (16 * vCH2) + 2 * (vCH3) # make SLDs - thisMask = deut[contrast] + thisMask = deut[contrast - 1] if thisMask[0] == 0: thisWater = (H2O * 0.9249) + (D2O * 0.0871) @@ -57,7 +60,7 @@ def Model_IIb(params, bulk_in, bulk_out, contrast): thisWater = D2O # Calculate mole fraction of D2O from the bulk SLD - d2o_molfr = (1 / D2O - H2O) * ((bulk_out[contrast] / 0.036182336306) - H2O) + d2o_molfr = (1 / D2O - H2O) * ((bulk_out[contrast - 1] / 0.036182336306) - H2O) thisWater = (d2o_molfr * D2O) + ((1 - d2o_molfr) * H2O) if thisMask[1] == 0: diff --git a/ratapi/examples/domains/alloy_domains.py b/ratapi/examples/domains/alloy_domains.py index 33454dcc..90da84fd 100644 --- a/ratapi/examples/domains/alloy_domains.py +++ b/ratapi/examples/domains/alloy_domains.py @@ -7,6 +7,9 @@ def alloy_domains(params, bulkIn, bulkOut, contrast, domain): Simple custom model for testing incoherent summing. Simple two layer of permalloy / gold, with up/down domains. """ + # Note - The first contrast number is 1 (not 0) so be careful if you use + # this variable for array indexing. Same applies to the domain number. + # Split up the parameters subRough = params[0] alloyThick = params[1] @@ -23,9 +26,9 @@ def alloy_domains(params, bulkIn, bulkOut, contrast, domain): gold = [goldThick, goldSLD, goldRough] # Make the model depending on which domain we are looking at - if domain == 0: + if domain == 1: output = [alloyUp, gold] - else: + elif domain == 2: output = [alloyDn, gold] return output, subRough diff --git a/ratapi/examples/domains/domains_XY_model.py b/ratapi/examples/domains/domains_XY_model.py index 00567666..6a288ba2 100644 --- a/ratapi/examples/domains/domains_XY_model.py +++ b/ratapi/examples/domains/domains_XY_model.py @@ -8,6 +8,9 @@ def domains_XY_model(params, bulk_in, bulk_out, contrast, domain): """Calculate the SLD profile for a domains custom XY model.""" + # Note - The first contrast number is 1 (not 0) so be careful if you use + # this variable for array indexing. Same applies to the domain number. + # Split up the parameters for convenience subRough = params[0] oxideThick = params[1] @@ -37,13 +40,13 @@ def domains_XY_model(params, bulk_in, bulk_out, contrast, domain): oxSLD = vfOxide * 3.41e-6 # Layer SLD depends on whether we are calculating the domain or not - if domain == 0: + if domain == 1: laySLD = vfLayer * layerSLD - else: + elif domain == 2: laySLD = vfLayer * domainSLD # ... and finally the water SLD. - waterSLD = vfWater * bulk_out[contrast] + waterSLD = vfWater * bulk_out[contrast - 1] # Make the total SLD by just adding them all up totalSLD = siSLD + oxSLD + laySLD + waterSLD diff --git a/ratapi/examples/languages/custom_bilayer.cpp b/ratapi/examples/languages/custom_bilayer.cpp index ad25b18f..2cf0ca81 100644 --- a/ratapi/examples/languages/custom_bilayer.cpp +++ b/ratapi/examples/languages/custom_bilayer.cpp @@ -13,6 +13,8 @@ extern "C" { LIB_EXPORT void custom_bilayer(std::vector& params, std::vector& bulkIn, std::vector& bulkOut, int contrast, std::vector& output, double* outputSize, double* rough) { + // Note - The first contrast number is 1 (not 0) so be careful if you use + // this variable for array indexing. double subRough = params[0]; double oxideThick = params[1]; double oxideHydration = params[2]; @@ -65,9 +67,9 @@ extern "C" { // Manually deal with hydration for layers in // this example. - double oxSLD = (oxideHydration * bulkOut[contrast]) + ((1 - oxideHydration) * oxideSLD); - double headSLD = (headHydration * bulkOut[contrast]) + ((1 - headHydration) * SLDhead); - double tailSLD = (bilayerHydration * bulkOut[contrast]) + ((1 - bilayerHydration) * SLDtail); + double oxSLD = (oxideHydration * bulkOut[contrast-1]) + ((1 - oxideHydration) * oxideSLD); + double headSLD = (headHydration * bulkOut[contrast-1]) + ((1 - headHydration) * SLDhead); + double tailSLD = (bilayerHydration * bulkOut[contrast-1]) + ((1 - bilayerHydration) * SLDtail); // Make the layers // oxide... @@ -77,7 +79,7 @@ extern "C" { // Water... output.push_back(waterThick); - output.push_back(bulkOut[contrast]); + output.push_back(bulkOut[contrast-1]); output.push_back(bilayerRough); // Heads... diff --git a/ratapi/examples/languages/custom_bilayer.py b/ratapi/examples/languages/custom_bilayer.py index 1777b358..bed82ca3 100644 --- a/ratapi/examples/languages/custom_bilayer.py +++ b/ratapi/examples/languages/custom_bilayer.py @@ -5,6 +5,9 @@ def custom_bilayer(params, bulk_in, bulk_out, contrast): """Calculate the layer parameters for a custom bilayer model.""" + # Note - The first contrast number is 1 (not 0) so be careful if you use + # this variable for array indexing. + sub_rough = params[0] oxide_thick = params[1] oxide_hydration = params[2] @@ -54,13 +57,13 @@ def custom_bilayer(params, bulk_in, bulk_out, contrast): tailThick = vTail / lipidAPM # Manually deal with hydration for layers in this example. - oxSLD = (oxide_hydration * bulk_out[contrast]) + ((1 - oxide_hydration) * oxide_SLD) - headSLD = (headHydration * bulk_out[contrast]) + ((1 - headHydration) * SLDhead) - tailSLD = (bilayerHydration * bulk_out[contrast]) + ((1 - bilayerHydration) * SLDtail) + oxSLD = (oxide_hydration * bulk_out[contrast - 1]) + ((1 - oxide_hydration) * oxide_SLD) + headSLD = (headHydration * bulk_out[contrast - 1]) + ((1 - headHydration) * SLDhead) + tailSLD = (bilayerHydration * bulk_out[contrast - 1]) + ((1 - bilayerHydration) * SLDtail) # Make the layers oxide = [oxide_thick, oxSLD, sub_rough] - water = [waterThick, bulk_out[contrast], bilayerRough] + water = [waterThick, bulk_out[contrast - 1], bilayerRough] head = [headThick, headSLD, bilayerRough] tail = [tailThick, tailSLD, bilayerRough] diff --git a/ratapi/examples/normal_reflectivity/DSPC_custom_layers.ipynb b/ratapi/examples/normal_reflectivity/DSPC_custom_layers.ipynb index d2fd6ed3..b90c4aa3 100644 --- a/ratapi/examples/normal_reflectivity/DSPC_custom_layers.ipynb +++ b/ratapi/examples/normal_reflectivity/DSPC_custom_layers.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "4b988c4a-3a09-4b75-8a87-8ba8402635ba", "metadata": {}, "outputs": [], @@ -29,12 +29,13 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "9a60cd45-0e1d-448a-b4bd-4c02bd6a3475", "metadata": {}, "outputs": [], "source": [ - "problem = RAT.Project(name=\"Orso lipid example - custom layers\", model=\"custom layers\", geometry=\"substrate/liquid\")" + "problem = RAT.Project(name=\"Orso lipid example - custom layers\", model=\"custom layers\", geometry=\"substrate/liquid\")\n", + "problem.show_priors()" ] }, { @@ -61,356 +62,10 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "9038b77f-e3fc-4946-87fe-af4addf8ee84", "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
import numpy as np\n",
-       "\n",
-       "\n",
-       "def custom_bilayer_DSPC(params, bulk_in, bulk_out, contrast):\n",
-       "    """CUSTOMBILAYER RAT Custom Layer Model File.\n",
-       "\n",
-       "    This file accepts 3 vectors containing the values for params, bulk in and bulk out.\n",
-       "    The final parameter is an index of the contrast being calculated.\n",
-       "\n",
-       "    The function should output a matrix of layer values, in the form...\n",
-       "\n",
-       "    Output = [thick 1, SLD 1, Rough 1, Percent Hydration 1, Hydrate how 1\n",
-       "              ....\n",
-       "              thick n, SLD n, Rough n, Percent Hydration n, Hydration how n]\n",
-       "\n",
-       "    The "hydrate how" parameter decides if the layer is hydrated with Bulk out or Bulk in phases.\n",
-       "    Set to 1 for Bulk out, zero for Bulk in.\n",
-       "    Alternatively, leave out hydration and just return...\n",
-       "\n",
-       "    Output = [thick 1, SLD 1, Rough 1,\n",
-       "              ....\n",
-       "              thick n, SLD n, Rough n]\n",
-       "\n",
-       "    The second output parameter should be the substrate roughness.\n",
-       "    """\n",
-       "    sub_rough = params[0]\n",
-       "    oxide_thick = params[1]\n",
-       "    oxide_hydration = params[2]\n",
-       "    lipidAPM = params[3]\n",
-       "    headHydration = params[4]\n",
-       "    bilayerHydration = params[5]\n",
-       "    bilayerRough = params[6]\n",
-       "    waterThick = params[7]\n",
-       "\n",
-       "    # We have a constant SLD for the bilayer\n",
-       "    oxide_SLD = 3.41e-6\n",
-       "\n",
-       "    # Now make the lipid layers\n",
-       "    # Use known lipid volume and compositions to make the layers\n",
-       "\n",
-       "    # define all the neutron b's.\n",
-       "    bc = 0.6646e-4  # Carbon\n",
-       "    bo = 0.5843e-4  # Oxygen\n",
-       "    bh = -0.3739e-4  # Hydrogen\n",
-       "    bp = 0.513e-4  # Phosphorus\n",
-       "    bn = 0.936e-4  # Nitrogen\n",
-       "\n",
-       "    # Now make the lipid groups\n",
-       "    COO = (4 * bo) + (2 * bc)\n",
-       "    GLYC = (3 * bc) + (5 * bh)\n",
-       "    CH3 = (2 * bc) + (6 * bh)\n",
-       "    PO4 = (1 * bp) + (4 * bo)\n",
-       "    CH2 = (1 * bc) + (2 * bh)\n",
-       "    CHOL = (5 * bc) + (12 * bh) + (1 * bn)\n",
-       "\n",
-       "    # Group these into heads and tails:\n",
-       "    Head = CHOL + PO4 + GLYC + COO\n",
-       "    Tails = (34 * CH2) + (2 * CH3)\n",
-       "\n",
-       "    # We need volumes for each. Use literature values:\n",
-       "    vHead = 319\n",
-       "    vTail = 782\n",
-       "\n",
-       "    # We use the volumes to calculate the SLDs\n",
-       "    SLDhead = Head / vHead\n",
-       "    SLDtail = Tails / vTail\n",
-       "\n",
-       "    # We calculate the layer thickness' from the volumes and the APM\n",
-       "    headThick = vHead / lipidAPM\n",
-       "    tailThick = vTail / lipidAPM\n",
-       "\n",
-       "    # Manually deal with hydration for layers in this example.\n",
-       "    oxSLD = (oxide_hydration * bulk_out[contrast]) + ((1 - oxide_hydration) * oxide_SLD)\n",
-       "    headSLD = (headHydration * bulk_out[contrast]) + ((1 - headHydration) * SLDhead)\n",
-       "    tailSLD = (bilayerHydration * bulk_out[contrast]) + ((1 - bilayerHydration) * SLDtail)\n",
-       "\n",
-       "    # Make the layers\n",
-       "    oxide = [oxide_thick, oxSLD, sub_rough]\n",
-       "    water = [waterThick, bulk_out[contrast], bilayerRough]\n",
-       "    head = [headThick, headSLD, bilayerRough]\n",
-       "    tail = [tailThick, tailSLD, bilayerRough]\n",
-       "\n",
-       "    output = np.array([oxide, water, head, tail, tail, head])\n",
-       "\n",
-       "    return output, sub_rough\n",
-       "
\n" - ], - "text/latex": [ - "\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n", - "\\PY{k+kn}{import} \\PY{n+nn}{numpy} \\PY{k}{as} \\PY{n+nn}{np}\n", - "\n", - "\n", - "\\PY{k}{def} \\PY{n+nf}{custom\\PYZus{}bilayer\\PYZus{}DSPC}\\PY{p}{(}\\PY{n}{params}\\PY{p}{,} \\PY{n}{bulk\\PYZus{}in}\\PY{p}{,} \\PY{n}{bulk\\PYZus{}out}\\PY{p}{,} \\PY{n}{contrast}\\PY{p}{)}\\PY{p}{:}\n", - "\\PY{+w}{ }\\PY{l+s+sd}{\\PYZdq{}\\PYZdq{}\\PYZdq{}CUSTOMBILAYER RAT Custom Layer Model File.}\n", - "\n", - "\\PY{l+s+sd}{ This file accepts 3 vectors containing the values for params, bulk in and bulk out.}\n", - "\\PY{l+s+sd}{ The final parameter is an index of the contrast being calculated.}\n", - "\n", - "\\PY{l+s+sd}{ The function should output a matrix of layer values, in the form...}\n", - "\n", - "\\PY{l+s+sd}{ Output = [thick 1, SLD 1, Rough 1, Percent Hydration 1, Hydrate how 1}\n", - "\\PY{l+s+sd}{ ....}\n", - "\\PY{l+s+sd}{ thick n, SLD n, Rough n, Percent Hydration n, Hydration how n]}\n", - "\n", - "\\PY{l+s+sd}{ The \\PYZdq{}hydrate how\\PYZdq{} parameter decides if the layer is hydrated with Bulk out or Bulk in phases.}\n", - "\\PY{l+s+sd}{ Set to 1 for Bulk out, zero for Bulk in.}\n", - "\\PY{l+s+sd}{ Alternatively, leave out hydration and just return...}\n", - "\n", - "\\PY{l+s+sd}{ Output = [thick 1, SLD 1, Rough 1,}\n", - "\\PY{l+s+sd}{ ....}\n", - "\\PY{l+s+sd}{ thick n, SLD n, Rough n]}\n", - "\n", - "\\PY{l+s+sd}{ The second output parameter should be the substrate roughness.}\n", - "\\PY{l+s+sd}{ \\PYZdq{}\\PYZdq{}\\PYZdq{}}\n", - " \\PY{n}{sub\\PYZus{}rough} \\PY{o}{=} \\PY{n}{params}\\PY{p}{[}\\PY{l+m+mi}{0}\\PY{p}{]}\n", - " \\PY{n}{oxide\\PYZus{}thick} \\PY{o}{=} \\PY{n}{params}\\PY{p}{[}\\PY{l+m+mi}{1}\\PY{p}{]}\n", - " \\PY{n}{oxide\\PYZus{}hydration} \\PY{o}{=} \\PY{n}{params}\\PY{p}{[}\\PY{l+m+mi}{2}\\PY{p}{]}\n", - " \\PY{n}{lipidAPM} \\PY{o}{=} \\PY{n}{params}\\PY{p}{[}\\PY{l+m+mi}{3}\\PY{p}{]}\n", - " \\PY{n}{headHydration} \\PY{o}{=} \\PY{n}{params}\\PY{p}{[}\\PY{l+m+mi}{4}\\PY{p}{]}\n", - " \\PY{n}{bilayerHydration} \\PY{o}{=} \\PY{n}{params}\\PY{p}{[}\\PY{l+m+mi}{5}\\PY{p}{]}\n", - " \\PY{n}{bilayerRough} \\PY{o}{=} \\PY{n}{params}\\PY{p}{[}\\PY{l+m+mi}{6}\\PY{p}{]}\n", - " \\PY{n}{waterThick} \\PY{o}{=} \\PY{n}{params}\\PY{p}{[}\\PY{l+m+mi}{7}\\PY{p}{]}\n", - "\n", - " \\PY{c+c1}{\\PYZsh{} We have a constant SLD for the bilayer}\n", - " \\PY{n}{oxide\\PYZus{}SLD} \\PY{o}{=} \\PY{l+m+mf}{3.41e\\PYZhy{}6}\n", - "\n", - " \\PY{c+c1}{\\PYZsh{} Now make the lipid layers}\n", - " \\PY{c+c1}{\\PYZsh{} Use known lipid volume and compositions to make the layers}\n", - "\n", - " \\PY{c+c1}{\\PYZsh{} define all the neutron b\\PYZsq{}s.}\n", - " \\PY{n}{bc} \\PY{o}{=} \\PY{l+m+mf}{0.6646e\\PYZhy{}4} \\PY{c+c1}{\\PYZsh{} Carbon}\n", - " \\PY{n}{bo} \\PY{o}{=} \\PY{l+m+mf}{0.5843e\\PYZhy{}4} \\PY{c+c1}{\\PYZsh{} Oxygen}\n", - " \\PY{n}{bh} \\PY{o}{=} \\PY{o}{\\PYZhy{}}\\PY{l+m+mf}{0.3739e\\PYZhy{}4} \\PY{c+c1}{\\PYZsh{} Hydrogen}\n", - " \\PY{n}{bp} \\PY{o}{=} \\PY{l+m+mf}{0.513e\\PYZhy{}4} \\PY{c+c1}{\\PYZsh{} Phosphorus}\n", - " \\PY{n}{bn} \\PY{o}{=} \\PY{l+m+mf}{0.936e\\PYZhy{}4} \\PY{c+c1}{\\PYZsh{} Nitrogen}\n", - "\n", - " \\PY{c+c1}{\\PYZsh{} Now make the lipid groups}\n", - " \\PY{n}{COO} \\PY{o}{=} \\PY{p}{(}\\PY{l+m+mi}{4} \\PY{o}{*} \\PY{n}{bo}\\PY{p}{)} \\PY{o}{+} \\PY{p}{(}\\PY{l+m+mi}{2} \\PY{o}{*} \\PY{n}{bc}\\PY{p}{)}\n", - " \\PY{n}{GLYC} \\PY{o}{=} \\PY{p}{(}\\PY{l+m+mi}{3} \\PY{o}{*} \\PY{n}{bc}\\PY{p}{)} \\PY{o}{+} \\PY{p}{(}\\PY{l+m+mi}{5} \\PY{o}{*} \\PY{n}{bh}\\PY{p}{)}\n", - " \\PY{n}{CH3} \\PY{o}{=} \\PY{p}{(}\\PY{l+m+mi}{2} \\PY{o}{*} \\PY{n}{bc}\\PY{p}{)} \\PY{o}{+} \\PY{p}{(}\\PY{l+m+mi}{6} \\PY{o}{*} \\PY{n}{bh}\\PY{p}{)}\n", - " \\PY{n}{PO4} \\PY{o}{=} \\PY{p}{(}\\PY{l+m+mi}{1} \\PY{o}{*} \\PY{n}{bp}\\PY{p}{)} \\PY{o}{+} \\PY{p}{(}\\PY{l+m+mi}{4} \\PY{o}{*} \\PY{n}{bo}\\PY{p}{)}\n", - " \\PY{n}{CH2} \\PY{o}{=} \\PY{p}{(}\\PY{l+m+mi}{1} \\PY{o}{*} \\PY{n}{bc}\\PY{p}{)} \\PY{o}{+} \\PY{p}{(}\\PY{l+m+mi}{2} \\PY{o}{*} \\PY{n}{bh}\\PY{p}{)}\n", - " \\PY{n}{CHOL} \\PY{o}{=} \\PY{p}{(}\\PY{l+m+mi}{5} \\PY{o}{*} \\PY{n}{bc}\\PY{p}{)} \\PY{o}{+} \\PY{p}{(}\\PY{l+m+mi}{12} \\PY{o}{*} \\PY{n}{bh}\\PY{p}{)} \\PY{o}{+} \\PY{p}{(}\\PY{l+m+mi}{1} \\PY{o}{*} \\PY{n}{bn}\\PY{p}{)}\n", - "\n", - " \\PY{c+c1}{\\PYZsh{} Group these into heads and tails:}\n", - " \\PY{n}{Head} \\PY{o}{=} \\PY{n}{CHOL} \\PY{o}{+} \\PY{n}{PO4} \\PY{o}{+} \\PY{n}{GLYC} \\PY{o}{+} \\PY{n}{COO}\n", - " \\PY{n}{Tails} \\PY{o}{=} \\PY{p}{(}\\PY{l+m+mi}{34} \\PY{o}{*} \\PY{n}{CH2}\\PY{p}{)} \\PY{o}{+} \\PY{p}{(}\\PY{l+m+mi}{2} \\PY{o}{*} \\PY{n}{CH3}\\PY{p}{)}\n", - "\n", - " \\PY{c+c1}{\\PYZsh{} We need volumes for each. Use literature values:}\n", - " \\PY{n}{vHead} \\PY{o}{=} \\PY{l+m+mi}{319}\n", - " \\PY{n}{vTail} \\PY{o}{=} \\PY{l+m+mi}{782}\n", - "\n", - " \\PY{c+c1}{\\PYZsh{} We use the volumes to calculate the SLDs}\n", - " \\PY{n}{SLDhead} \\PY{o}{=} \\PY{n}{Head} \\PY{o}{/} \\PY{n}{vHead}\n", - " \\PY{n}{SLDtail} \\PY{o}{=} \\PY{n}{Tails} \\PY{o}{/} \\PY{n}{vTail}\n", - "\n", - " \\PY{c+c1}{\\PYZsh{} We calculate the layer thickness\\PYZsq{} from the volumes and the APM}\n", - " \\PY{n}{headThick} \\PY{o}{=} \\PY{n}{vHead} \\PY{o}{/} \\PY{n}{lipidAPM}\n", - " \\PY{n}{tailThick} \\PY{o}{=} \\PY{n}{vTail} \\PY{o}{/} \\PY{n}{lipidAPM}\n", - "\n", - " \\PY{c+c1}{\\PYZsh{} Manually deal with hydration for layers in this example.}\n", - " \\PY{n}{oxSLD} \\PY{o}{=} \\PY{p}{(}\\PY{n}{oxide\\PYZus{}hydration} \\PY{o}{*} \\PY{n}{bulk\\PYZus{}out}\\PY{p}{[}\\PY{n}{contrast}\\PY{p}{]}\\PY{p}{)} \\PY{o}{+} \\PY{p}{(}\\PY{p}{(}\\PY{l+m+mi}{1} \\PY{o}{\\PYZhy{}} \\PY{n}{oxide\\PYZus{}hydration}\\PY{p}{)} \\PY{o}{*} \\PY{n}{oxide\\PYZus{}SLD}\\PY{p}{)}\n", - " \\PY{n}{headSLD} \\PY{o}{=} \\PY{p}{(}\\PY{n}{headHydration} \\PY{o}{*} \\PY{n}{bulk\\PYZus{}out}\\PY{p}{[}\\PY{n}{contrast}\\PY{p}{]}\\PY{p}{)} \\PY{o}{+} \\PY{p}{(}\\PY{p}{(}\\PY{l+m+mi}{1} \\PY{o}{\\PYZhy{}} \\PY{n}{headHydration}\\PY{p}{)} \\PY{o}{*} \\PY{n}{SLDhead}\\PY{p}{)}\n", - " \\PY{n}{tailSLD} \\PY{o}{=} \\PY{p}{(}\\PY{n}{bilayerHydration} \\PY{o}{*} \\PY{n}{bulk\\PYZus{}out}\\PY{p}{[}\\PY{n}{contrast}\\PY{p}{]}\\PY{p}{)} \\PY{o}{+} \\PY{p}{(}\\PY{p}{(}\\PY{l+m+mi}{1} \\PY{o}{\\PYZhy{}} \\PY{n}{bilayerHydration}\\PY{p}{)} \\PY{o}{*} \\PY{n}{SLDtail}\\PY{p}{)}\n", - "\n", - " \\PY{c+c1}{\\PYZsh{} Make the layers}\n", - " \\PY{n}{oxide} \\PY{o}{=} \\PY{p}{[}\\PY{n}{oxide\\PYZus{}thick}\\PY{p}{,} \\PY{n}{oxSLD}\\PY{p}{,} \\PY{n}{sub\\PYZus{}rough}\\PY{p}{]}\n", - " \\PY{n}{water} \\PY{o}{=} \\PY{p}{[}\\PY{n}{waterThick}\\PY{p}{,} \\PY{n}{bulk\\PYZus{}out}\\PY{p}{[}\\PY{n}{contrast}\\PY{p}{]}\\PY{p}{,} \\PY{n}{bilayerRough}\\PY{p}{]}\n", - " \\PY{n}{head} \\PY{o}{=} \\PY{p}{[}\\PY{n}{headThick}\\PY{p}{,} \\PY{n}{headSLD}\\PY{p}{,} \\PY{n}{bilayerRough}\\PY{p}{]}\n", - " \\PY{n}{tail} \\PY{o}{=} \\PY{p}{[}\\PY{n}{tailThick}\\PY{p}{,} \\PY{n}{tailSLD}\\PY{p}{,} \\PY{n}{bilayerRough}\\PY{p}{]}\n", - "\n", - " \\PY{n}{output} \\PY{o}{=} \\PY{n}{np}\\PY{o}{.}\\PY{n}{array}\\PY{p}{(}\\PY{p}{[}\\PY{n}{oxide}\\PY{p}{,} \\PY{n}{water}\\PY{p}{,} \\PY{n}{head}\\PY{p}{,} \\PY{n}{tail}\\PY{p}{,} \\PY{n}{tail}\\PY{p}{,} \\PY{n}{head}\\PY{p}{]}\\PY{p}{)}\n", - "\n", - " \\PY{k}{return} \\PY{n}{output}\\PY{p}{,} \\PY{n}{sub\\PYZus{}rough}\n", - "\\end{Verbatim}\n" - ], - "text/plain": [ - "import numpy as np\n", - "\n", - "\n", - "def custom_bilayer_DSPC(params, bulk_in, bulk_out, contrast):\n", - " \"\"\"CUSTOMBILAYER RAT Custom Layer Model File.\n", - "\n", - " This file accepts 3 vectors containing the values for params, bulk in and bulk out.\n", - " The final parameter is an index of the contrast being calculated.\n", - "\n", - " The function should output a matrix of layer values, in the form...\n", - "\n", - " Output = [thick 1, SLD 1, Rough 1, Percent Hydration 1, Hydrate how 1\n", - " ....\n", - " thick n, SLD n, Rough n, Percent Hydration n, Hydration how n]\n", - "\n", - " The \"hydrate how\" parameter decides if the layer is hydrated with Bulk out or Bulk in phases.\n", - " Set to 1 for Bulk out, zero for Bulk in.\n", - " Alternatively, leave out hydration and just return...\n", - "\n", - " Output = [thick 1, SLD 1, Rough 1,\n", - " ....\n", - " thick n, SLD n, Rough n]\n", - "\n", - " The second output parameter should be the substrate roughness.\n", - " \"\"\"\n", - " sub_rough = params[0]\n", - " oxide_thick = params[1]\n", - " oxide_hydration = params[2]\n", - " lipidAPM = params[3]\n", - " headHydration = params[4]\n", - " bilayerHydration = params[5]\n", - " bilayerRough = params[6]\n", - " waterThick = params[7]\n", - "\n", - " # We have a constant SLD for the bilayer\n", - " oxide_SLD = 3.41e-6\n", - "\n", - " # Now make the lipid layers\n", - " # Use known lipid volume and compositions to make the layers\n", - "\n", - " # define all the neutron b's.\n", - " bc = 0.6646e-4 # Carbon\n", - " bo = 0.5843e-4 # Oxygen\n", - " bh = -0.3739e-4 # Hydrogen\n", - " bp = 0.513e-4 # Phosphorus\n", - " bn = 0.936e-4 # Nitrogen\n", - "\n", - " # Now make the lipid groups\n", - " COO = (4 * bo) + (2 * bc)\n", - " GLYC = (3 * bc) + (5 * bh)\n", - " CH3 = (2 * bc) + (6 * bh)\n", - " PO4 = (1 * bp) + (4 * bo)\n", - " CH2 = (1 * bc) + (2 * bh)\n", - " CHOL = (5 * bc) + (12 * bh) + (1 * bn)\n", - "\n", - " # Group these into heads and tails:\n", - " Head = CHOL + PO4 + GLYC + COO\n", - " Tails = (34 * CH2) + (2 * CH3)\n", - "\n", - " # We need volumes for each. Use literature values:\n", - " vHead = 319\n", - " vTail = 782\n", - "\n", - " # We use the volumes to calculate the SLDs\n", - " SLDhead = Head / vHead\n", - " SLDtail = Tails / vTail\n", - "\n", - " # We calculate the layer thickness' from the volumes and the APM\n", - " headThick = vHead / lipidAPM\n", - " tailThick = vTail / lipidAPM\n", - "\n", - " # Manually deal with hydration for layers in this example.\n", - " oxSLD = (oxide_hydration * bulk_out[contrast]) + ((1 - oxide_hydration) * oxide_SLD)\n", - " headSLD = (headHydration * bulk_out[contrast]) + ((1 - headHydration) * SLDhead)\n", - " tailSLD = (bilayerHydration * bulk_out[contrast]) + ((1 - bilayerHydration) * SLDtail)\n", - "\n", - " # Make the layers\n", - " oxide = [oxide_thick, oxSLD, sub_rough]\n", - " water = [waterThick, bulk_out[contrast], bilayerRough]\n", - " head = [headThick, headSLD, bilayerRough]\n", - " tail = [tailThick, tailSLD, bilayerRough]\n", - "\n", - " output = np.array([oxide, water, head, tail, tail, head])\n", - "\n", - " return output, sub_rough" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "Code(filename='custom_bilayer_DSPC.py', language='python')" ] @@ -425,7 +80,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "70494ef9-6cc5-47dc-9d02-6506645de46b", "metadata": {}, "outputs": [], @@ -454,7 +109,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "453fe3d2-162a-42bb-91ee-b1d020ffd29e", "metadata": {}, "outputs": [], @@ -478,7 +133,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "fa4c1b96-3a1b-4aa6-8d61-68f24b0cb482", "metadata": {}, "outputs": [], @@ -505,7 +160,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "2e649c26-b32b-4c79-8ae7-fa701c87e6c2", "metadata": {}, "outputs": [], @@ -523,7 +178,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "5d51954f-469a-4044-9a7d-1b6e30474a6b", "metadata": {}, "outputs": [], @@ -554,7 +209,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "b1e4d313-8450-459b-b60e-868fe82f06b0", "metadata": {}, "outputs": [], @@ -572,7 +227,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "efc7b351-2112-40c4-862b-a47e4570d173", "metadata": {}, "outputs": [], @@ -623,141 +278,10 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "ee889e55-8357-4363-860d-fb1c13bb8e8b", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Name: ----------------------------------------------------------------------------------------------\n", - "\n", - "Orso lipid example - custom layers\n", - "\n", - "Calculation: ---------------------------------------------------------------------------------------\n", - "\n", - "non polarised\n", - "\n", - "Model: ---------------------------------------------------------------------------------------------\n", - "\n", - "custom layers\n", - "\n", - "Geometry: ------------------------------------------------------------------------------------------\n", - "\n", - "substrate/liquid\n", - "\n", - "Parameters: ----------------------------------------------------------------------------------------\n", - "\n", - "+-------+---------------------+------+-------+------+------+------------+-----+-------+\n", - "| index | name | min | value | max | fit | prior type | mu | sigma |\n", - "+-------+---------------------+------+-------+------+------+------------+-----+-------+\n", - "| 0 | Substrate Roughness | 1.0 | 3.0 | 10.0 | True | uniform | 0.0 | inf |\n", - "| 1 | Oxide Thickness | 5.0 | 20.0 | 60.0 | True | uniform | 0.0 | inf |\n", - "| 2 | Oxide Hydration | 0.0 | 0.2 | 0.5 | True | uniform | 0.0 | inf |\n", - "| 3 | Lipid APM | 45.0 | 55.0 | 65.0 | True | uniform | 0.0 | inf |\n", - "| 4 | Head Hydration | 0.0 | 0.2 | 0.5 | True | gaussian | 0.3 | 0.03 |\n", - "| 5 | Bilayer Hydration | 0.0 | 0.1 | 0.2 | True | uniform | 0.0 | inf |\n", - "| 6 | Bilayer Roughness | 2.0 | 4.0 | 8.0 | True | uniform | 0.0 | inf |\n", - "| 7 | Water Thickness | 0.0 | 2.0 | 10.0 | True | uniform | 0.0 | inf |\n", - "+-------+---------------------+------+-------+------+------+------------+-----+-------+\n", - "\n", - "Bulk In: -------------------------------------------------------------------------------------------\n", - "\n", - "+-------+---------+----------+-----------+----------+-------+------------+-----+-------+\n", - "| index | name | min | value | max | fit | prior type | mu | sigma |\n", - "+-------+---------+----------+-----------+----------+-------+------------+-----+-------+\n", - "| 0 | Silicon | 2.07e-06 | 2.073e-06 | 2.08e-06 | False | uniform | 0.0 | inf |\n", - "+-------+---------+----------+-----------+----------+-------+------------+-----+-------+\n", - "\n", - "Bulk Out: ------------------------------------------------------------------------------------------\n", - "\n", - "+-------+---------+--------+-----------+----------+------+------------+-----+-------+\n", - "| index | name | min | value | max | fit | prior type | mu | sigma |\n", - "+-------+---------+--------+-----------+----------+------+------------+-----+-------+\n", - "| 0 | SLD D2O | 5e-06 | 6.35e-06 | 6.35e-06 | True | uniform | 0.0 | inf |\n", - "| 1 | SLD SMW | 1e-06 | 2.073e-06 | 3e-06 | True | uniform | 0.0 | inf |\n", - "| 2 | SLD H2O | -6e-07 | -5.6e-07 | -3e-07 | True | uniform | 0.0 | inf |\n", - "+-------+---------+--------+-----------+----------+------+------------+-----+-------+\n", - "\n", - "Scalefactors: --------------------------------------------------------------------------------------\n", - "\n", - "+-------+---------------+-----+-------+-----+------+------------+-----+-------+\n", - "| index | name | min | value | max | fit | prior type | mu | sigma |\n", - "+-------+---------------+-----+-------+-----+------+------------+-----+-------+\n", - "| 0 | Scalefactor 1 | 0.5 | 1.0 | 2.0 | True | uniform | 0.0 | inf |\n", - "+-------+---------------+-----+-------+-----+------+------------+-----+-------+\n", - "\n", - "Background Parameters: -----------------------------------------------------------------------------\n", - "\n", - "+-------+--------------------------+-------+-------+-------+------+------------+-----+-------+\n", - "| index | name | min | value | max | fit | prior type | mu | sigma |\n", - "+-------+--------------------------+-------+-------+-------+------+------------+-----+-------+\n", - "| 0 | Background parameter D2O | 1e-10 | 1e-07 | 1e-05 | True | uniform | 0.0 | inf |\n", - "| 1 | Background parameter SMW | 1e-10 | 1e-07 | 1e-05 | True | uniform | 0.0 | inf |\n", - "| 2 | Background parameter H2O | 1e-10 | 1e-07 | 1e-05 | True | uniform | 0.0 | inf |\n", - "+-------+--------------------------+-------+-------+-------+------+------------+-----+-------+\n", - "\n", - "Backgrounds: ---------------------------------------------------------------------------------------\n", - "\n", - "+-------+----------------+----------+--------------------------+---------+---------+---------+---------+\n", - "| index | name | type | value 1 | value 2 | value 3 | value 4 | value 5 |\n", - "+-------+----------------+----------+--------------------------+---------+---------+---------+---------+\n", - "| 0 | Background D2O | constant | Background parameter D2O | | | | |\n", - "| 1 | Background SMW | constant | Background parameter SMW | | | | |\n", - "| 2 | Background H2O | constant | Background parameter H2O | | | | |\n", - "+-------+----------------+----------+--------------------------+---------+---------+---------+---------+\n", - "\n", - "Resolution Parameters: -----------------------------------------------------------------------------\n", - "\n", - "+-------+--------------------+------+-------+------+-------+------------+-----+-------+\n", - "| index | name | min | value | max | fit | prior type | mu | sigma |\n", - "+-------+--------------------+------+-------+------+-------+------------+-----+-------+\n", - "| 0 | Resolution Param 1 | 0.01 | 0.03 | 0.05 | False | uniform | 0.0 | inf |\n", - "+-------+--------------------+------+-------+------+-------+------------+-----+-------+\n", - "\n", - "Resolutions: ---------------------------------------------------------------------------------------\n", - "\n", - "+-------+-----------------+----------+--------------------+---------+---------+---------+---------+\n", - "| index | name | type | value 1 | value 2 | value 3 | value 4 | value 5 |\n", - "+-------+-----------------+----------+--------------------+---------+---------+---------+---------+\n", - "| 0 | Resolution 1 | constant | Resolution Param 1 | | | | |\n", - "| 1 | Data Resolution | data | | | | | |\n", - "+-------+-----------------+----------+--------------------+---------+---------+---------+---------+\n", - "\n", - "Custom Files: --------------------------------------------------------------------------------------\n", - "\n", - "+-------+------------+------------------------+---------------------+----------+-------------------------------------------------------------------------+\n", - "| index | name | filename | function name | language | path |\n", - "+-------+------------+------------------------+---------------------+----------+-------------------------------------------------------------------------+\n", - "| 0 | DSPC Model | custom_bilayer_DSPC.py | custom_bilayer_DSPC | python | /mnt/c/Users/gnn85523/projects/python-RAT/ratapi/examples/non_polarised |\n", - "+-------+------------+------------------------+---------------------+----------+-------------------------------------------------------------------------+\n", - "\n", - "Data: ----------------------------------------------------------------------------------------------\n", - "\n", - "+-------+---------------+-----------------------+------------------+----------------------+\n", - "| index | name | data | data range | simulation range |\n", - "+-------+---------------+-----------------------+------------------+----------------------+\n", - "| 0 | Simulation | [] | [] | [0.005, 0.7] |\n", - "| 1 | Bilayer / D2O | Data array: [146 x 4] | [0.013, 0.37] | [0.0057118, 0.39606] |\n", - "| 2 | Bilayer / SMW | Data array: [97 x 4] | [0.013, 0.32996] | [0.0076029, 0.32996] |\n", - "| 3 | Bilayer / H2O | Data array: [104 x 4] | [0.013, 0.33048] | [0.0063374, 0.33048] |\n", - "+-------+---------------+-----------------------+------------------+----------------------+\n", - "\n", - "Contrasts: -----------------------------------------------------------------------------------------\n", - "\n", - "+-------+---------------+---------------+----------------+-------------------+---------+----------+---------------+-----------------+----------+------------+\n", - "| index | name | data | background | background action | bulk in | bulk out | scalefactor | resolution | resample | model |\n", - "+-------+---------------+---------------+----------------+-------------------+---------+----------+---------------+-----------------+----------+------------+\n", - "| 0 | Bilayer / D2O | Bilayer / D2O | Background D2O | add | Silicon | SLD D2O | Scalefactor 1 | Data Resolution | False | DSPC Model |\n", - "| 1 | Bilayer / SMW | Bilayer / SMW | Background SMW | add | Silicon | SLD SMW | Scalefactor 1 | Data Resolution | False | DSPC Model |\n", - "| 2 | Bilayer / H2O | Bilayer / H2O | Background H2O | add | Silicon | SLD H2O | Scalefactor 1 | Data Resolution | False | DSPC Model |\n", - "+-------+---------------+---------------+----------------+-------------------+---------+----------+---------------+-----------------+----------+------------+\n", - "\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "print(problem)" ] @@ -772,27 +296,10 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "154a33df-06b9-4035-aa4c-a0e095c1bb06", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "+------------------+-----------+\n", - "| Property | Value |\n", - "+------------------+-----------+\n", - "| procedure | calculate |\n", - "| parallel | single |\n", - "| calcSldDuringFit | False |\n", - "| resampleMinAngle | 0.9 |\n", - "| resampleNPoints | 50 |\n", - "| display | iter |\n", - "+------------------+-----------+\n" - ] - } - ], + "outputs": [], "source": [ "controls = RAT.Controls()\n", "print(controls)" @@ -808,33 +315,10 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "d5d9a782-0fb1-40b6-b1fa-86307abe32a6", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Starting RAT ───────────────────────────────────────────────────────────────────────────────────────────────────────────\n", - "\n", - "Elapsed time is 0.020 seconds\n", - "\n", - "Finished RAT ───────────────────────────────────────────────────────────────────────────────────────────────────────────\n", - "\n" - ] - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "problem, results = RAT.run(problem, controls)\n", "RAT.plotting.plot_ref_sld(problem, results)" @@ -857,7 +341,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.10.16" } }, "nbformat": 4, diff --git a/ratapi/examples/normal_reflectivity/custom_XY_DSPC.py b/ratapi/examples/normal_reflectivity/custom_XY_DSPC.py index 93e25b08..c57adecf 100644 --- a/ratapi/examples/normal_reflectivity/custom_XY_DSPC.py +++ b/ratapi/examples/normal_reflectivity/custom_XY_DSPC.py @@ -8,6 +8,9 @@ def custom_XY_DSPC(params, bulk_in, bulk_out, contrast): """Calculate the continuous SLD of a supported DSPC bilayer using volume restricted distribution functions.""" + # Note - The first contrast number is 1 (not 0) so be careful if you use + # this variable for array indexing. + # Split up the parameters subRough = params[0] oxideThick = params[1] @@ -109,7 +112,7 @@ def custom_XY_DSPC(params, bulk_in, bulk_out, contrast): sldHeadL = vfHeadL * sld_Value_Head sldHeadR = vfHeadR * sld_Value_Head sldTails = vfTails * sld_Value_Tails - sldWat = vfWat * bulk_out[contrast] + sldWat = vfWat * bulk_out[contrast - 1] # Put this all together totSLD = sldSilicon + sldOxide + sldHeadL + sldTails + sldHeadR + sldWat diff --git a/ratapi/examples/normal_reflectivity/custom_bilayer_DSPC.py b/ratapi/examples/normal_reflectivity/custom_bilayer_DSPC.py index 005147ff..8646de71 100644 --- a/ratapi/examples/normal_reflectivity/custom_bilayer_DSPC.py +++ b/ratapi/examples/normal_reflectivity/custom_bilayer_DSPC.py @@ -25,6 +25,8 @@ def custom_bilayer_DSPC(params, bulk_in, bulk_out, contrast): The second output parameter should be the substrate roughness. """ + # Note - The first contrast number is 1 (not 0) so be careful if you use + # this variable for array indexing. sub_rough = params[0] oxide_thick = params[1] oxide_hydration = params[2] @@ -72,13 +74,13 @@ def custom_bilayer_DSPC(params, bulk_in, bulk_out, contrast): tailThick = vTail / lipidAPM # Manually deal with hydration for layers in this example. - oxSLD = (oxide_hydration * bulk_out[contrast]) + ((1 - oxide_hydration) * oxide_SLD) - headSLD = (headHydration * bulk_out[contrast]) + ((1 - headHydration) * SLDhead) - tailSLD = (bilayerHydration * bulk_out[contrast]) + ((1 - bilayerHydration) * SLDtail) + oxSLD = (oxide_hydration * bulk_out[contrast - 1]) + ((1 - oxide_hydration) * oxide_SLD) + headSLD = (headHydration * bulk_out[contrast - 1]) + ((1 - headHydration) * SLDhead) + tailSLD = (bilayerHydration * bulk_out[contrast - 1]) + ((1 - bilayerHydration) * SLDtail) # Make the layers oxide = [oxide_thick, oxSLD, sub_rough] - water = [waterThick, bulk_out[contrast], bilayerRough] + water = [waterThick, bulk_out[contrast - 1], bilayerRough] head = [headThick, headSLD, bilayerRough] tail = [tailThick, tailSLD, bilayerRough] diff --git a/ratapi/utils/plotting.py b/ratapi/utils/plotting.py index d4a81e1a..dc1ade53 100644 --- a/ratapi/utils/plotting.py +++ b/ratapi/utils/plotting.py @@ -53,7 +53,7 @@ def _extract_plot_data(event_data: PlotEventData, q4: bool, show_error_bar: bool zip(event_data.reflectivity, event_data.shiftedData, event_data.sldProfiles, strict=False) ): # Calculate the divisor - div = 1 if i == 0 and not q4 else 10 ** ((i / 100) * shift_value) + div = 1 if i == 0 and not q4 else 10 ** ((i + 1) / 100 * shift_value) q4_data = 1 if not q4 or not event_data.dataPresent[i] else data[:, 0] ** 4 mult = q4_data / div @@ -63,14 +63,17 @@ def _extract_plot_data(event_data: PlotEventData, q4: bool, show_error_bar: bool if event_data.dataPresent[i]: sd_x = data[:, 0] sd_y, sd_e = map(lambda x: x * mult, (data[:, 1], data[:, 2])) + errors = np.zeros(len(sd_e)) if show_error_bar: - errors = np.zeros(len(sd_e)) valid = sd_y - sd_e >= 0 errors[valid] = sd_e[valid] valid |= sd_y < 0 + else: + valid = np.ones(len(sd_e)).astype(bool) + sd_e = errors - results["error"].append([sd_x[valid], sd_y[valid], sd_e[valid]]) + results["error"].append([sd_x[valid], sd_y[valid], sd_e[valid]]) results["sld"].append([]) for j in range(len(sld)): @@ -145,7 +148,7 @@ def plot_ref_sld_helper( fig.clf() fig.subplots(1, 2) - fig.subplots_adjust(wspace=0.3) + fig.subplots_adjust(wspace=0.3, hspace=0) ref_plot: plt.Axes = fig.axes[0] sld_plot: plt.Axes = fig.axes[1] @@ -170,7 +173,7 @@ def plot_ref_sld_helper( mult = (1 if not q4 else plot_data["ref"][i][0] ** 4) / div ref_plot.fill_between(plot_data["ref"][i][0], ref_min * mult, ref_max * mult, alpha=0.6, color="grey") - if data.dataPresent[i] and show_error_bar: + if data.dataPresent[i]: # Plot the errorbars ref_plot.errorbar( x=plot_data["error"][i][0], @@ -529,7 +532,7 @@ def update_foreground(self, data): self.figure.canvas.restore_region(self.bg) plot_data = _extract_plot_data(data, self.q4, self.show_error_bar, self.shift_value) - offset = 2 if self.show_error_bar else 1 + offset = 2 for i in range( 0, len(self.figure.axes[0].lines), diff --git a/ratapi/wrappers.py b/ratapi/wrappers.py index 9475b93e..38f276ac 100644 --- a/ratapi/wrappers.py +++ b/ratapi/wrappers.py @@ -78,10 +78,10 @@ def handle(*args): np.array(args[0], "float"), # params np.array(args[1], "float"), # bulk in np.array(args[2], "float"), # bulk out - float(args[3] + 1), # contrast + float(args[3]), # contrast ] if len(args) > 4: - matlab_args.append(float(args[4] + 1)) # domain number + matlab_args.append(float(args[4])) # domain number output, sub_rough = getattr(self.engine, self.function_name)( *matlab_args, diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py index 680b198b..782e833c 100644 --- a/tests/test_wrappers.py +++ b/tests/test_wrappers.py @@ -25,13 +25,13 @@ def test_matlab_wrapper() -> None: handle = wrapper.getHandle() mocked_engine.demo.return_value = ([2], 5) - result = handle([1], [2], [3], 0) + result = handle([1], [2], [3], 1) assert result == ([2], 5) assert wrapper.engine.demo.call_args[0] == ([1], [2], [3], 1) mocked_engine.demo.assert_called_once() mocked_engine.demo.return_value = ([3, 1], 7) - result = handle([4], [5], [6], 1, 1) + result = handle([4], [5], [6], 2, 2) assert result == ([3, 1], 7) assert wrapper.engine.demo.call_args[0] == ([4], [5], [6], 2, 2) assert mocked_engine.demo.call_count == 2 From a599d448bfd469755eb67a84af0a874fd55e5fe1 Mon Sep 17 00:00:00 2001 From: StephenNneji <34302892+StephenNneji@users.noreply.github.com> Date: Mon, 16 Feb 2026 11:49:16 +0000 Subject: [PATCH 3/9] Allow relative paths for custom file (#198) --- ratapi/models.py | 8 +------- ratapi/project.py | 10 ++++++++-- tests/conftest.py | 8 ++++++++ tests/test_models.py | 11 +++++------ tests/test_project.py | 20 +++++++++++--------- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/ratapi/models.py b/ratapi/models.py index 7d0d4f74..755ea974 100644 --- a/ratapi/models.py +++ b/ratapi/models.py @@ -313,13 +313,7 @@ class CustomFile(RATModel): filename: str = "" function_name: str = "" language: Languages = Languages.Python - path: pathlib.Path = pathlib.Path(".").resolve() - - @field_validator("path") - @classmethod - def resolve_relative_paths(cls, path: pathlib.Path) -> pathlib.Path: - """Return the absolute path of the given path.""" - return path.resolve() + path: pathlib.Path = pathlib.Path(".") def model_post_init(self, __context: Any) -> None: """Autogenerate the function name from the filename if not set. diff --git a/ratapi/project.py b/ratapi/project.py index fcbbaf83..b8ca49f2 100644 --- a/ratapi/project.py +++ b/ratapi/project.py @@ -956,12 +956,16 @@ def make_data_dict(item): elif field == "custom_files": def make_custom_file_dict(item): - return { + file_dict = { "name": item.name, "filename": item.filename, "language": item.language, "path": try_relative_to(item.path, filepath.parent), } + if item.name != item.function_name: + file_dict["function_name"] = item.function_name + + return file_dict json_dict["custom_files"] = [make_custom_file_dict(file) for file in attr] @@ -1062,7 +1066,9 @@ def try_relative_to(path: Path, relative_to: Path) -> str: """ path = Path(path) relative_to = Path(relative_to) - if path.is_relative_to(relative_to): + if not path.is_absolute(): + return str(path) + elif path.is_relative_to(relative_to): return str(path.relative_to(relative_to)) else: warnings.warn( diff --git a/tests/conftest.py b/tests/conftest.py index b717ec4e..0f3b3569 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8537,3 +8537,11 @@ def absorption(): """The project from the absorption example.""" project, _ = ratapi.examples.absorption() return project + + +@pytest.fixture +def absorption_different_function(): + """The project from the absorption example with a function name different from filename.""" + project, _ = ratapi.examples.absorption() + project.custom_files[0].function_name = "test_func" + return project diff --git a/tests/test_models.py b/tests/test_models.py index 906a5d67..949cf5aa 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,6 +1,5 @@ """Test the pydantic models.""" -import pathlib import re from collections.abc import Callable @@ -101,11 +100,11 @@ def test_initialise_with_extra_fields(self, model: Callable, model_params: dict) model(new_field=1, **model_params) -def test_custom_file_path_is_absolute() -> None: - """If we use provide a relative path to the custom file model, it should be converted to an absolute path.""" - relative_path = pathlib.Path("./relative_path") - custom_file = ratapi.models.CustomFile(path=relative_path) - assert custom_file.path.is_absolute() +# def test_custom_file_path_is_absolute() -> None: +# """If we use provide a relative path to the custom file model, it should be converted to an absolute path.""" +# relative_path = pathlib.Path("./relative_path") +# custom_file = ratapi.models.CustomFile(path=relative_path) +# assert custom_file.path.is_absolute() def test_data_eq() -> None: diff --git a/tests/test_project.py b/tests/test_project.py index 31e13760..35d5864e 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -1616,6 +1616,7 @@ def test_wrap_extend(test_project, class_list: str, model_type: str, field: str, "domains_custom_layers", "domains_custom_xy", "absorption", + "absorption_different_function", ], ) def test_save_load(project, request): @@ -1637,24 +1638,25 @@ def test_save_load(project, request): def test_relative_paths(): """Test that ``try_relative_to`` correctly creates relative paths to subfolders.""" - with tempfile.TemporaryDirectory() as tmp: - data_path = Path(tmp, "data/myfile.dat") + cur_path = Path(".").resolve() + data_path = cur_path / "data/myfile.dat" + assert Path(ratapi.project.try_relative_to(data_path, cur_path)) == Path("data/myfile.dat") - assert Path(ratapi.project.try_relative_to(data_path, tmp)) == Path("data/myfile.dat") + # relative path will be left relative. + data_path = "data/myfile.dat" + assert Path(ratapi.project.try_relative_to(data_path, cur_path)) == Path("data/myfile.dat") def test_relative_paths_warning(): """Test that we get a warning for trying to walk up paths.""" - data_path = "/tmp/project/data/mydata.dat" - relative_path = "/tmp/project/project_path/myproj.dat" + cur_path = Path(".").resolve() + data_path = cur_path / "tmp/project/data/mydata.dat" + relative_path = cur_path / "tmp/project/project_path/myproj.dat" with pytest.warns( match="Could not write custom file path as relative to the project directory, " "which means that it may not work on other devices. If you would like to share your project, " "make sure your custom files are in a subfolder of the project save location.", ): - assert ( - Path(ratapi.project.try_relative_to(data_path, relative_path)) - == Path("/tmp/project/data/mydata.dat").resolve() - ) + assert Path(ratapi.project.try_relative_to(data_path, relative_path)) == data_path From 3d12df45aa2cbab1af063831ecf5b587df608a98 Mon Sep 17 00:00:00 2001 From: StephenNneji <34302892+StephenNneji@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:36:07 +0000 Subject: [PATCH 4/9] Fixes auto name bug, adds clearer error for missing custom file or function name and update to dev12 (#199) --- cpp/RAT | 2 +- cpp/rat.cpp | 4 +-- pyproject.toml | 2 +- ratapi/inputs.py | 7 ++++ ratapi/models.py | 84 ++++++++++++++++++++++++++++++++++---------- tests/test_inputs.py | 26 ++++++++++++++ tests/test_models.py | 22 ++++++------ 7 files changed, 114 insertions(+), 33 deletions(-) diff --git a/cpp/RAT b/cpp/RAT index 7da24c8d..a5d4d80b 160000 --- a/cpp/RAT +++ b/cpp/RAT @@ -1 +1 @@ -Subproject commit 7da24c8d9700bff1eb5c660f5389214c473a1c24 +Subproject commit a5d4d80ba63185846db678c3ebd9a85bdd82921d diff --git a/cpp/rat.cpp b/cpp/rat.cpp index 31bbe144..957ba4fa 100644 --- a/cpp/rat.cpp +++ b/cpp/rat.cpp @@ -525,8 +525,8 @@ OutputBayesResult OutputBayesResultsFromStruct(const RAT::BayesResults results) bayesResults.confidenceIntervals.percentile65 = pyArrayFromRatArray2d(results.confidenceIntervals.percentile65); bayesResults.confidenceIntervals.mean = pyArrayFromRatArray2d(results.confidenceIntervals.mean); - bayesResults.nestedSamplerOutput.logZ = results.nestedSamplerOutput.LogZ; - bayesResults.nestedSamplerOutput.logZErr = results.nestedSamplerOutput.LogZErr; + bayesResults.nestedSamplerOutput.logZ = results.nestedSamplerOutput.logZ; + bayesResults.nestedSamplerOutput.logZErr = results.nestedSamplerOutput.logZErr; bayesResults.nestedSamplerOutput.nestSamples = pyArrayFromRatArray2d(results.nestedSamplerOutput.nestSamples); bayesResults.nestedSamplerOutput.postSamples = pyArrayFromRatArray2d(results.nestedSamplerOutput.postSamples); diff --git a/pyproject.toml b/pyproject.toml index 85d6e161..c68585c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = 'setuptools.build_meta' [project] name = "ratapi" -version = "0.0.0.dev11" +version = "0.0.0.dev12" description = "Python extension for the Reflectivity Analysis Toolbox (RAT)" readme = "README.md" requires-python = ">=3.10" diff --git a/ratapi/inputs.py b/ratapi/inputs.py index 921890b5..bdfaddad 100644 --- a/ratapi/inputs.py +++ b/ratapi/inputs.py @@ -77,6 +77,13 @@ def get_handle(self, index: int): """ custom_file = self.files[index] full_path = os.path.join(custom_file["path"], custom_file["filename"]) + + if not os.path.isfile(full_path): + raise FileNotFoundError(f"The custom file ({custom_file['name']}) does not have a valid path.") + + if not custom_file["function_name"] and custom_file["language"] != Languages.Matlab: + raise ValueError(f"The custom file ({custom_file['name']}) does not have a valid function name.") + if custom_file["language"] == Languages.Python: file_handle = get_python_handle(custom_file["filename"], custom_file["function_name"], custom_file["path"]) elif custom_file["language"] == Languages.Matlab: diff --git a/ratapi/models.py b/ratapi/models.py index 755ea974..ba0a1e99 100644 --- a/ratapi/models.py +++ b/ratapi/models.py @@ -2,7 +2,7 @@ import pathlib import warnings -from itertools import count +from contextlib import suppress from typing import Any import numpy as np @@ -18,14 +18,41 @@ # Create a counter for each model -background_number = count(1) -contrast_number = count(1) -custom_file_number = count(1) -data_number = count(1) -domain_contrast_number = count(1) -layer_number = count(1) -parameter_number = count(1) -resolution_number = count(1) +background_number = ["Background", 0] +contrast_number = ["Contrast", 0] +custom_file_number = ["Custom File", 0] +data_number = ["Data", 0] +domain_contrast_number = ["Domain Contrast", 0] +layer_number = ["Layer", 0] +parameter_number = ["Parameter", 0] +resolution_number = ["Resolution", 0] + +_model_counter = { + "Background": background_number, + "Contrast": contrast_number, + "ContrastWithRatio": contrast_number, + "CustomFile": custom_file_number, + "Data": data_number, + "DomainContrast": domain_contrast_number, + "Layer": layer_number, + "AbsorptionLayer": layer_number, + "Parameter": parameter_number, + "ProtectedParameter": parameter_number, + "Resolution": resolution_number, +} + + +def _model_name_factory(model_name: str) -> str: + """Generate a unique name for model using a global counter. + + Parameters + ---------- + model_name : str + The name of the model class. + """ + title, number = _model_counter[model_name] + _model_counter[model_name][1] += 1 + return f"New {title} {(number + 1)}" class RATModel(BaseModel, validate_assignment=True, extra="forbid"): @@ -38,6 +65,25 @@ def __repr__(self): ) return f"{self.__repr_name__()}({fields_repr})" + @field_validator("name", mode="after", check_fields=False) + @classmethod + def update_counter(cls, name: str) -> str: + """Update the auto name counter if a similar name is manually given. + + Parameters + ---------- + name : str + The name of the model. + """ + title, number = _model_counter[cls.__name__] + prefix = f"New {title} " + if name.startswith(prefix): + with suppress(ValueError): + new_number = int(name[len(prefix) :]) + if new_number > number: + _model_counter[cls.__name__][1] = new_number + return name + def __str__(self): table = prettytable.PrettyTable() table.field_names = [key.replace("_", " ") for key in self.display_fields] @@ -116,7 +162,7 @@ class Background(Signal): """ - name: str = Field(default_factory=lambda: f"New Background {next(background_number)}", min_length=1) + name: str = Field(default_factory=lambda: _model_name_factory("Background"), min_length=1) @model_validator(mode="after") def check_unsupported_parameters(self): @@ -173,7 +219,7 @@ class Contrast(RATModel): """ - name: str = Field(default_factory=lambda: f"New Contrast {next(contrast_number)}", min_length=1) + name: str = Field(default_factory=lambda: _model_name_factory("Contrast"), min_length=1) data: str = "" background: str = "" background_action: BackgroundActions = BackgroundActions.Add @@ -255,7 +301,7 @@ class ContrastWithRatio(RATModel): """ - name: str = Field(default_factory=lambda: f"New Contrast {next(contrast_number)}", min_length=1) + name: str = Field(default_factory=lambda: _model_name_factory("ContrastWithRatio"), min_length=1) data: str = "" background: str = "" background_action: BackgroundActions = BackgroundActions.Add @@ -309,7 +355,7 @@ class CustomFile(RATModel): """ - name: str = Field(default_factory=lambda: f"New Custom File {next(custom_file_number)}", min_length=1) + name: str = Field(default_factory=lambda: _model_name_factory("CustomFile"), min_length=1) filename: str = "" function_name: str = "" language: Languages = Languages.Python @@ -348,7 +394,7 @@ class Data(RATModel, arbitrary_types_allowed=True): """ - name: str = Field(default_factory=lambda: f"New Data {next(data_number)}", min_length=1) + name: str = Field(default_factory=lambda: _model_name_factory("Data"), min_length=1) data: np.ndarray = np.empty([0, 3]) data_range: list[float] = Field(default=[], min_length=2, max_length=2) simulation_range: list[float] = Field(default=[], min_length=2, max_length=2) @@ -453,7 +499,7 @@ class DomainContrast(RATModel): """ - name: str = Field(default_factory=lambda: f"New Domain Contrast {next(domain_contrast_number)}", min_length=1) + name: str = Field(default_factory=lambda: _model_name_factory("DomainContrast"), min_length=1) model: list[str] = [] def __str__(self): @@ -483,7 +529,7 @@ class Layer(RATModel, populate_by_name=True): """ - name: str = Field(default_factory=lambda: f"New Layer {next(layer_number)}", min_length=1) + name: str = Field(default_factory=lambda: _model_name_factory("Layer"), min_length=1) thickness: str SLD: str = Field(validation_alias="SLD_real") roughness: str @@ -522,7 +568,7 @@ class AbsorptionLayer(RATModel, populate_by_name=True): """ - name: str = Field(default_factory=lambda: f"New Layer {next(layer_number)}", min_length=1) + name: str = Field(default_factory=lambda: _model_name_factory("AbsorptionLayer"), min_length=1) thickness: str SLD_real: str = Field(validation_alias="SLD") SLD_imaginary: str @@ -555,7 +601,7 @@ class Parameter(RATModel): """ - name: str = Field(default_factory=lambda: f"New Parameter {next(parameter_number)}", min_length=1) + name: str = Field(default_factory=lambda: _model_name_factory("Parameter"), min_length=1) min: float = 0.0 value: float = 0.0 max: float = 0.0 @@ -638,7 +684,7 @@ class Resolution(Signal): """ - name: str = Field(default_factory=lambda: f"New Resolution {next(resolution_number)}", min_length=1) + name: str = Field(default_factory=lambda: _model_name_factory("Resolution"), min_length=1) @field_validator("type") @classmethod diff --git a/tests/test_inputs.py b/tests/test_inputs.py index 244fd4d2..242d7480 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -2,6 +2,8 @@ import pathlib import pickle +import tempfile +from unittest.mock import patch import numpy as np import pytest @@ -675,6 +677,30 @@ def test_make_controls(standard_layers_controls) -> None: check_controls_equal(controls, standard_layers_controls) +@patch("ratapi.wrappers.MatlabWrapper") +def test_file_handles(wrapper): + handle = FileHandles([ratapi.models.CustomFile(name="Test Custom File", filename="cpp_test.dll", language="cpp")]) + + with pytest.raises(FileNotFoundError, match="The custom file \\(Test Custom File\\) does not have a valid path."): + handle.get_handle(0) + + with tempfile.NamedTemporaryFile("w", suffix=".dll") as f: + tmp_file = pathlib.Path(f.name) + handle.files[0]["path"] = tmp_file.parent + handle.files[0]["filename"] = tmp_file.name + handle.files[0]["function_name"] = "" + # No function name should throw exception + with pytest.raises( + ValueError, match="The custom file \\(Test Custom File\\) does not have a valid function name." + ): + handle.get_handle(0) + + # Matlab does not need function name + handle.files[0]["language"] = "matlab" + handle.get_handle(0) + wrapper.assert_called() + + def check_problem_equal(actual_problem, expected_problem) -> None: """Compare two instances of the "problem" object for equality.""" scalar_fields = [ diff --git a/tests/test_models.py b/tests/test_models.py index 949cf5aa..4343b442 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -32,14 +32,23 @@ def test_default_names(model: Callable, model_name: str, model_params: dict) -> format: "New ". """ model_1 = model(**model_params) + prefix = f"New {model_name} " + assert model_1.name.startswith(prefix) + index = int(model_1.name[len(prefix) :]) + model_2 = model(**model_params) model_3 = model(name="Given Name", **model_params) model_4 = model(**model_params) - assert model_1.name == f"New {model_name} 1" - assert model_2.name == f"New {model_name} 2" + assert model_1.name == f"New {model_name} {index}" + assert model_2.name == f"New {model_name} {index + 1}" assert model_3.name == "Given Name" - assert model_4.name == f"New {model_name} 3" + assert model_4.name == f"New {model_name} {index + 2}" + + # If user adds name in similar format. The next auto number will take it into account. + model(name=f"{prefix}{index + 20}", **model_params) + model_5 = model(**model_params) + assert model_5.name == f"New {model_name} {index + 21}" @pytest.mark.parametrize( @@ -100,13 +109,6 @@ def test_initialise_with_extra_fields(self, model: Callable, model_params: dict) model(new_field=1, **model_params) -# def test_custom_file_path_is_absolute() -> None: -# """If we use provide a relative path to the custom file model, it should be converted to an absolute path.""" -# relative_path = pathlib.Path("./relative_path") -# custom_file = ratapi.models.CustomFile(path=relative_path) -# assert custom_file.path.is_absolute() - - def test_data_eq() -> None: """If we use the Data.__eq__ method with an object that is not a pydantic BaseModel, we should return "NotImplemented". From 18862ca3332b550fae1fd2845c7b1b1b7b2ebb93 Mon Sep 17 00:00:00 2001 From: StephenNneji <34302892+StephenNneji@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:40:21 +0000 Subject: [PATCH 5/9] Change pybind build version, blit plot bug and update to dev13 (#200) --- pyproject.toml | 4 ++-- ratapi/utils/plotting.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c68585c3..09796c44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,13 +2,13 @@ requires = [ 'setuptools>=61', 'wheel', - 'pybind11>=2.4', + 'pybind11>=2.4, <=2.13.6', ] build-backend = 'setuptools.build_meta' [project] name = "ratapi" -version = "0.0.0.dev12" +version = "0.0.0.dev13" description = "Python extension for the Reflectivity Analysis Toolbox (RAT)" readme = "README.md" requires-python = ">=3.10" diff --git a/ratapi/utils/plotting.py b/ratapi/utils/plotting.py index dc1ade53..8225bd43 100644 --- a/ratapi/utils/plotting.py +++ b/ratapi/utils/plotting.py @@ -505,6 +505,7 @@ def update_plot(self, data): show_error_bar=self.show_error_bar, show_grid=self.show_grid, show_legend=self.show_legend, + shift_value=self.shift_value, animated=True, ) self.figure.canvas.draw() From 072c9325e1a63b3fca4625231abbda6749cba28d Mon Sep 17 00:00:00 2001 From: StephenNneji <34302892+StephenNneji@users.noreply.github.com> Date: Wed, 11 Mar 2026 09:27:43 +0000 Subject: [PATCH 6/9] Use latest version of cibuildwheel (#201) --- .github/workflows/build_wheel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 03176b7b..688118ed 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -67,7 +67,7 @@ jobs: export PATH="$pythonLocation:$PATH" CIBW_TEST_COMMAND='cd ${pwd}/tmp && python -m pytest tests' echo "CIBW_TEST_COMMAND=${CIBW_TEST_COMMAND}" >> $GITHUB_ENV - python -m pip install cibuildwheel==2.23.3 + python -m pip install cibuildwheel python -m cibuildwheel --output-dir ./wheelhouse - uses: actions/upload-artifact@v4 with: From 06926937c54b6e8ecb5c0b44089ca5e45f045d2d Mon Sep 17 00:00:00 2001 From: StephenNneji <34302892+StephenNneji@users.noreply.github.com> Date: Tue, 24 Mar 2026 09:40:56 +0000 Subject: [PATCH 7/9] Fix shift value for confidence interval (#202) --- ratapi/utils/plotting.py | 16 ++++++++-------- tests/test_plotting.py | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/ratapi/utils/plotting.py b/ratapi/utils/plotting.py index 8225bd43..6f9debb7 100644 --- a/ratapi/utils/plotting.py +++ b/ratapi/utils/plotting.py @@ -36,7 +36,7 @@ def _extract_plot_data(event_data: PlotEventData, q4: bool, show_error_bar: bool show_error_bar : bool, default: True Controls whether the error bars are shown shift_value : float - A value between 1 and 100 that controls the spacing between the reflectivity plots for each of the contrasts + A value between 0 and 100 that controls the spacing between the reflectivity plots for each of the contrasts Returns ------- @@ -46,14 +46,14 @@ def _extract_plot_data(event_data: PlotEventData, q4: bool, show_error_bar: bool """ results = {"ref": [], "error": [], "sld": [], "sld_resample": []} - if shift_value < 1 or shift_value > 100: - raise ValueError("Parameter `shift_value` must be between 1 and 100") + if shift_value < 0 or shift_value > 100: + raise ValueError("Parameter `shift_value` must be between 0 and 100") for i, (r, data, sld) in enumerate( zip(event_data.reflectivity, event_data.shiftedData, event_data.sldProfiles, strict=False) ): # Calculate the divisor - div = 1 if i == 0 and not q4 else 10 ** ((i + 1) / 100 * shift_value) + div = 10 ** (i / 100 * shift_value) q4_data = 1 if not q4 or not event_data.dataPresent[i] else data[:, 0] ** 4 mult = q4_data / div @@ -137,7 +137,7 @@ def plot_ref_sld_helper( show_legend : bool, default: True Controls whether the legend is shown shift_value : float, default: 100 - A value between 1 and 100 that controls the spacing between the reflectivity plots for each of the contrasts + A value between 0 and 100 that controls the spacing between the reflectivity plots for each of the contrasts animated : bool, default: False Controls whether the animated property of foreground plot elements should be set. @@ -168,7 +168,7 @@ def plot_ref_sld_helper( # Plot confidence intervals if required if confidence_intervals is not None: # Calculate the divisor - div = 1 if i == 0 and not q4 else 10 ** ((i / 100) * shift_value) + div = 10 ** (i / 100 * shift_value) ref_min, ref_max = confidence_intervals["reflectivity"][i] mult = (1 if not q4 else plot_data["ref"][i][0] ** 4) / div ref_plot.fill_between(plot_data["ref"][i][0], ref_min * mult, ref_max * mult, alpha=0.6, color="grey") @@ -277,7 +277,7 @@ def plot_ref_sld( show_legend : bool, default: True Controls whether the legend is shown shift_value : float, default: 100 - A value between 1 and 100 that controls the spacing between the reflectivity plots for each of the contrasts + A value between 0 and 100 that controls the spacing between the reflectivity plots for each of the contrasts Returns ------- @@ -387,7 +387,7 @@ class BlittingSupport: show_legend : bool, default: True Controls whether the legend is shown shift_value : float, default: 100 - A value between 1 and 100 that controls the spacing between the reflectivity plots for each of the contrasts + A value between 0 and 100 that controls the spacing between the reflectivity plots for each of the contrasts """ def __init__( diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 710729a5..31049a28 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -490,8 +490,8 @@ def test_extract_plot_data(data) -> None: assert len(plot_data["ref"]) == len(data.reflectivity) assert len(plot_data["sld"]) == len(data.shiftedData) - with pytest.raises(ValueError, match=r"Parameter `shift_value` must be between 1 and 100"): - RATplot._extract_plot_data(data, False, True, 0) + with pytest.raises(ValueError, match=r"Parameter `shift_value` must be between 0 and 100"): + RATplot._extract_plot_data(data, False, True, -0.1) - with pytest.raises(ValueError, match=r"Parameter `shift_value` must be between 1 and 100"): + with pytest.raises(ValueError, match=r"Parameter `shift_value` must be between 0 and 100"): RATplot._extract_plot_data(data, False, True, 100.5) From f40513507428d3ff3bc0f1086531c472a52a044f Mon Sep 17 00:00:00 2001 From: StephenNneji <34302892+StephenNneji@users.noreply.github.com> Date: Wed, 1 Apr 2026 12:41:47 +0100 Subject: [PATCH 8/9] Update to dev14 (#203) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 09796c44..ab6724ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = 'setuptools.build_meta' [project] name = "ratapi" -version = "0.0.0.dev13" +version = "0.0.0.dev14" description = "Python extension for the Reflectivity Analysis Toolbox (RAT)" readme = "README.md" requires-python = ">=3.10" From b0f51a91ec3bc40f0e5ccd518f07b4ce946881fd Mon Sep 17 00:00:00 2001 From: StephenNneji <34302892+StephenNneji@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:13:01 +0100 Subject: [PATCH 9/9] Handle R1 contrast not being unique (#204) --- ratapi/utils/convert.py | 8 +++++++- tests/test_convert.py | 7 +++++++ tests/test_data/nonUniqueContrast.mat | Bin 0 -> 17500 bytes 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/test_data/nonUniqueContrast.mat diff --git a/ratapi/utils/convert.py b/ratapi/utils/convert.py index b957789c..7b3a44f7 100644 --- a/ratapi/utils/convert.py +++ b/ratapi/utils/convert.py @@ -5,7 +5,7 @@ from os import PathLike from pathlib import Path -from numpy import array, empty +from numpy import array, empty, ndarray from scipy.io.matlab import MatlabOpaque, loadmat from ratapi import Project, wrappers @@ -227,6 +227,12 @@ def fix_invalid_constraints(name: str, constrs: tuple[float, float], value: floa if isinstance(mat_project["resolNames"], str): mat_project["resolNames"] = [mat_project["resolNames"]] + if isinstance(mat_project["contrastNames"], (ndarray, list)) and len( + dict.fromkeys(mat_project["contrastNames"]) + ) != len(mat_project["contrastNames"]): + # contrast names are not unique so create unique ones + mat_project["contrastNames"] = [f"Contrast {i + 1}" for i in range(len(mat_project["contrastNames"]))] + contrasts = ClassList( [ Contrast( diff --git a/tests/test_convert.py b/tests/test_convert.py index fc6f4400..0cbd621b 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -69,6 +69,13 @@ def test_r1_to_project(file, project, path_type, request): assert getattr(output_project, class_list) == getattr(expected_project, class_list) +def test_r1_with_non_unique_contrast_names(): + """Test that R1 to Project class conversion returns the expected Project.""" + output_project = r1_to_project(pathlib.Path(TEST_DIR_PATH, "nonUniqueContrast.mat")) + assert output_project.contrasts[0].name == "Contrast 1" + assert output_project.contrasts[1].name == "Contrast 2" + + @pytest.mark.parametrize( "project", [ diff --git a/tests/test_data/nonUniqueContrast.mat b/tests/test_data/nonUniqueContrast.mat new file mode 100644 index 0000000000000000000000000000000000000000..4137513434671e945b287102fbdfa7df00cbd150 GIT binary patch literal 17500 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