Skip to content

Commit

Permalink
Merge branch 'master' into ticket_1156_lattice
Browse files Browse the repository at this point in the history
  • Loading branch information
pkienzle authored Nov 26, 2024
2 parents f549e7e + 049de31 commit 8050a44
Show file tree
Hide file tree
Showing 30 changed files with 727 additions and 146 deletions.
33 changes: 26 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,34 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
python-version: ["3.7", "3.8", "3.9", "3.10"]
python-version: ["3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Free Disk Space (Ubuntu)
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: jlumbroso/free-disk-space@main
with:
haskell: false
large-packages: false

- name: setup apt dependencies for Linux
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
sudo apt-get update
sudo apt-get install opencl-headers ocl-icd-opencl-dev libpocl2
sudo apt update
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip
python -m pip install wheel setuptools
python -m pip install mako
python -m pip install numpy scipy matplotlib docutils pytest sphinx bumps unittest-xml-reporting tinycc
python -m pip install numpy scipy matplotlib docutils pytest sphinx bumps==0.* unittest-xml-reporting tinycc siphash24
- name: setup pyopencl on Linux + macOS
if: ${{ matrix.os != 'windows-latest' }}
Expand All @@ -45,9 +51,11 @@ jobs:
choco install opencl-intel-cpu-runtime
python -m pip install --only-binary=pyopencl --find-links http://www.silx.org/pub/wheelhouse/ --trusted-host www.silx.org pyopencl
- name: Test with pytest
- name: Test with pytest (only on Windows for now since PoCL is failing on Ubuntu)

env:
PYOPENCL_COMPILER_OUTPUT: 1
SAS_OPENCL: none
run: |
# other CI uses the following, but `setup.py test` is a deprecated way
# of running tests
Expand All @@ -57,5 +65,16 @@ jobs:
- name: check that the docs build (linux only)
if: ${{ matrix.os == 'ubuntu-latest' }}
env:
SAS_OPENCL: none
run: |
make -j 4 -C doc SPHINXOPTS="-W --keep-going -n" html
- name: Publish samodels docs
if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.10'}}
uses: actions/upload-artifact@v3
with:
name: sasmodels-docs-${{ matrix.os }}-${{ matrix.python-version }}
path: |
doc/_build/html
if-no-files-found: error
10 changes: 9 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
Release notes
=============

v1.0.7 2023-02-??
v1.0.8 2024-09-26
-----------------
* New model: Bulk ferromagnets model from marketplace
* Doc update: Archive built docs on Github
* Doc update: Display math correctly
* Fix error in FCC paracrystaline models
* Fix parameter name checking in kernel call

v1.0.7 2023-03-23
------------------
* Doc upate: corefunc and optimizer documentation
* Doc update: various models (cylinder, gel_fit, paracrystal, core_shell_ellipsoid)
Expand Down
3 changes: 1 addition & 2 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
Copyright (c) 2009-2022, SasView Developers

Copyright (c) 2009-2024, SasView Developers

All rights reserved.

Expand Down
37 changes: 3 additions & 34 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

nitpick_ignore = [
('py:class', 'argparse.Namespace'),
('py:class', 'bumps.parameter.Parameter'),
('py:class', 'collections.OrderedDict'),
('py:class', 'cuda.Context'),
('py:class', 'cuda.Function'),
Expand All @@ -40,6 +41,7 @@
('py:class', 'pyopencl._cl.Device'),
('py:class', 'pyopencl._cl.Kernel'),
('py:class', 'QWebView'),
('py:class', 'types.ModuleType'),
('py:class', 'unittest.suite.TestSuite'),
('py:class', 'wx.Frame'),
# autodoc and namedtuple is completely broken
Expand All @@ -55,6 +57,7 @@
('py:class', 'module'),
('py:class', 'SesansData'),
('py:class', 'SourceModule'),
('py:class', 'TestCondition'),
# KernelModel and Calculator breaking on git actions tests, even though
# KernelModel is already listed. astropy example sometimes includes full
# path to complaining symbol. Let's see if that helps here:
Expand Down Expand Up @@ -141,40 +144,6 @@
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

nitpick_ignore = [
('py:class', 'argparse.Namespace'),
('py:class', 'bumps.parameter.Parameter'),
('py:class', 'collections.OrderedDict'),
('py:class', 'cuda.Context'),
('py:class', 'cuda.Function'),
('py:class', 'np.dtype'),
('py:class', 'numpy.dtype'),
('py:class', 'np.ndarray'),
('py:class', 'numpy.ndarray'),
('py:class', 'pyopencl.Program'),
('py:class', 'pyopencl._cl.Context'),
('py:class', 'pyopencl._cl.CommandQueue'),
('py:class', 'pyopencl._cl.Device'),
('py:class', 'pyopencl._cl.Kernel'),
('py:class', 'QWebView'),
('py:class', 'unittest.suite.TestSuite'),
('py:class', 'wx.Frame'),
# autodoc and namedtuple is completely broken
('py:class', 'integer -- return number of occurrences of value'),
('py:class', 'integer -- return first index of value.'),
# autodoc doesn't handle these type definitions
('py:class', 'Data'),
('py:class', 'Data1D'),
('py:class', 'Data2D'),
('py:class', 'Kernel'),
('py:class', 'ModelInfo'),
('py:class', 'module'),
('py:class', 'SesansData'),
('py:class', 'SourceModule'),
('py:class', 'TestCondition'),
]



# -- Options for HTML output ---------------------------------------------------

Expand Down
4 changes: 3 additions & 1 deletion doc/guide/plugin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ value at a time::

Iq.vectorized = False

Return np.NaN if the parameters are not valid (e.g., cap_radius < radius in
Return np.nan if the parameters are not valid (e.g., cap_radius < radius in
barbell). If I(q; pars) is NaN for any $q$, then those parameters will be
ignored, and not included in the calculation of the weighted polydispersity.

Expand Down Expand Up @@ -851,6 +851,8 @@ Some non-standard constants and functions are also provided:
$x^2$
cube(x):
$x^3$
clip(a, a_min, a_max):
$\min(\max(a, a_\text{min}), a_\text{max})$, or NaN if $a$ is NaN.
sas_sinx_x(x):
$\sin(x)/x$, with limit $\sin(0)/0 = 1$.
powr(x, y):
Expand Down
1 change: 1 addition & 0 deletions doc/guide/theory.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.. currentmodule:: sasmodels
.. theory.rst
.. Much of the following text was scraped from fitting_sq.py
Expand Down
54 changes: 51 additions & 3 deletions explore/precision.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,13 @@ def plotdiff(x, target, actual, label, diff):
if diff == "relative":
err = np.array([(abs((t-a)/t) if t != 0 else a) for t, a in zip(target, actual)], 'd')
#err = np.clip(err, 0, 1)
pylab.loglog(x, err, '-', label=label)
pylab.loglog(x, err, '-', label=label, alpha=0.7)
elif diff == "absolute":
err = np.array([abs((t-a)) for t, a in zip(target, actual)], 'd')
pylab.loglog(x, err, '-', label=label)
pylab.loglog(x, err, '-', label=label, alpha=0.7)
else:
limits = np.min(target), np.max(target)
pylab.semilogx(x, np.clip(actual, *limits), '-', label=label)
pylab.semilogx(x, np.clip(actual, *limits), '-', label=label, alpha=0.7)

def make_ocl(function, name, source=[]):
class Kernel(object):
Expand Down Expand Up @@ -412,6 +412,54 @@ def add_function(name, mp_function, np_function, ocl_function,
np_function=lambda x: np.fmod(x, 2*np.pi),
ocl_function=make_ocl("return fmod(q, 2*M_PI);", "sas_fmod"),
)

def sas_langevin(x):
scalar = np.isscalar(x)
if scalar:
x = np.array([x]) # should inherit dtype for single if given single
f = np.empty_like(x)
cutoff = 0.1 if f.dtype == np.float64 else 1.0
#cutoff *= 10
index = x < cutoff
xp = x[index]
xpsq = xp*xp
f[index] = xp / (3. + xpsq / (5. + xpsq/(7. + xpsq/(9.))))
# 4 terms gets to 1e-7 single, 1e-14 double. Can get to 1e-15 double by adding
# another 4 terms and setting cutoff at 1.0. Not worthwhile. Instead we would
# need an expansion about x somewhere between 1 and 10 for the interval [0.1, 100.]
#f[index] = xp / (3. + xpsq / (5. + xpsq/(7. + xpsq/(9. + xpsq/(11.0 + xpsq/(13. + xpsq/(15. + xpsq/17.)))))))
xp = x[~index]
f[~index] = 1/np.tanh(xp) - 1/xp
return f[0] if scalar else f

def sas_langevin_x(x):
scalar = np.isscalar(x)
if scalar:
x = np.array([x]) # should inherit dtype for single if given single
f = np.empty_like(x)
cutoff = 0.1 if f.dtype == np.float64 else 1.0
index = x < cutoff
xp = x[index]
xpsq = xp*xp
f[index] = 1. / (3. + xpsq / (5. + xpsq/(7. + xpsq/(9.))))
xp = x[~index]
f[~index] = (1/np.tanh(xp) - 1/xp)/xp
return f[0] if scalar else f

add_function(
name="langevin(x)",
mp_function=lambda x: (1/mp.tanh(x) - 1/x),
np_function=sas_langevin,
#ocl_function=make_ocl("return q < 0.7 ? q*(1./3. + q*q*(-1./45. + q*q*(2./945. + q*q*(-1./4725.) + q*q*(2./93555.)))) : 1/tanh(q) - 1/q;", "sas_langevin"),
ocl_function=make_ocl("return q < 1e-5 ? q/3. : 1/tanh(q) - 1/q;", "sas_langevin"),
)
add_function(
name="langevin(x)/x",
mp_function=lambda x: (1/mp.tanh(x) - 1/x)/x,
#np_function=lambda x: sas_langevin(x)/x, # Note: need to test for x=0
np_function=sas_langevin_x,
ocl_function=make_ocl("return q < 1e-5 ? 1./3. : (1/tanh(q) - 1/q)/q;", "sas_langevin_x"),
)
add_function(
name="gauss_coil",
mp_function=lambda x: 2*(mp.exp(-x**2) + x**2 - 1)/x**4,
Expand Down
4 changes: 2 additions & 2 deletions explore/realspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ def pad_vectors(boundary, *vectors):
if new_size > old_size:
new = np.empty(new_size, dtype=old.dtype)
new[:old_size] = old
new[old_size:] = np.NaN
new[old_size:] = np.nan
yield new
else:
yield old
Expand Down Expand Up @@ -1195,7 +1195,7 @@ def _sasmodels_Iqxy(kernel, qx, qy, pars, view):
# calculator avoids masked values; instead set masked values to NaN
result = np.empty_like(qx)
result[calculator.index] = Iqxy
result[~calculator.index] = np.NaN
result[~calculator.index] = np.nan
return result

def wrap_sasmodel(name, **pars):
Expand Down
2 changes: 1 addition & 1 deletion sasmodels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
OpenCL drivers are available. See :mod:`.generate` for details on
defining new models.
"""
__version__ = "1.0.6"
__version__ = "1.0.8"

def data_files():
"""
Expand Down
6 changes: 3 additions & 3 deletions sasmodels/compare_many.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,9 @@ def try_model(fn, pars):
traceback.print_exc()
print("when comparing %s for %d"%(name, seed))
if hasattr(data, 'qx_data'):
result = np.NaN*data.data
result = np.nan*data.data
else:
result = np.NaN*data.x
result = np.nan*data.x
return result
def check_model(pars):
"""
Expand All @@ -165,7 +165,7 @@ def check_model(pars):
except Exception as exc:
#raise
print('"Error: %s"'%str(exc).replace('"', "'"))
print('"good","%d of %d","max diff",%g' % (0, N, np.NaN))
print('"good","%d of %d","max diff",%g' % (0, N, np.nan))
return
expected = max(PRECISION[base], PRECISION[comp])

Expand Down
6 changes: 3 additions & 3 deletions sasmodels/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ def __init__(self, x=None, y=None, dx=None, dy=None):
self.y, self.dy = _as_numpy(y), _as_numpy(dy)
self.dxl = None
self.filename = None
self.qmin = self.x.min() if self.x is not None else np.NaN
self.qmax = self.x.max() if self.x is not None else np.NaN
self.qmin = self.x.min() if self.x is not None else np.nan
self.qmax = self.x.max() if self.x is not None else np.nan
# TODO: why is 1D mask False and 2D mask True?
self.mask = (np.isnan(y) if y is not None
else np.zeros_like(x, 'b') if x is not None
Expand Down Expand Up @@ -314,7 +314,7 @@ class Source(object):
"""
def __init__(self):
# type: () -> None
self.wavelength = np.NaN
self.wavelength = np.nan
self.wavelength_unit = "A"

class Sample(object):
Expand Down
22 changes: 15 additions & 7 deletions sasmodels/direct_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,10 @@ def get_mesh(model_info, values, dim='1d', mono=False):
values = values.copy()
mesh = [_pop_par_weights(p, values, active(p.name))
for p in parameters.call_parameters]

if values:
raise TypeError(f"Unused parameters in call: {', '.join(values.keys())}")

return mesh


Expand Down Expand Up @@ -561,16 +563,17 @@ def near(value, target):
return np.allclose(value, target, rtol=1e-6, atol=0, equal_nan=True)
# Note: target values taken from running main() on parameters.
# Resolution was 5% dq/q.
pars = dict(radius=200)
pars = dict(radius=200, background=0) # default background=1e-3, scale=1
# simple sphere in 1D (perfect, pinhole, slit)
assert near(Iq('sphere', [0.1], **pars), [0.6200146273894904])
assert near(Iq('sphere', [0.1], dq=[0.005], **pars), [2.3019224683980215])
assert near(Iq('sphere', [0.1], qw=[0.005], ql=[1.0], **pars), [0.3673431784535172])
perfect_target = 0.6190146273894904
assert near(Iq('sphere', [0.1], **pars), [perfect_target])
assert near(Iq('sphere', [0.1], dq=[0.005], **pars), [2.3009224683980215])
assert near(Iq('sphere', [0.1], qw=[0.005], ql=[1.0], **pars), [0.3663431784535172])
# simple sphere in 2D (perfect, pinhole)
assert near(Iqxy('sphere', [0.1], [0.1], **pars), [1.1781532874802199])
assert near(Iqxy('sphere', [0.1], [0.1], **pars), [1.1771532874802199])
assert near(Iqxy('sphere', [0.1], [0.1], dqx=[0.005], dqy=[0.005], **pars),
[0.8177780778578667])
# sesans
[0.8167780778578667])
# sesans (no background or scale)
assert near(Gxi('sphere', [100], **pars), [-0.19146959126623486])
# Check that single point sesans matches value in an array
xi = np.logspace(1, 3, 100)
Expand All @@ -587,6 +590,11 @@ def near(value, target):
radius=200, radius_pd=0.1, radius_pd_n=15, radius_pd_nsigma=2.5,
radius_pd_type="uniform")
assert near(Iq('sphere', [0.1], **pars), [2.703169824954617])
# background and scale
background, scale = 1e-4, 0.1
pars = dict(radius=200, background=background, scale=scale)
assert near(Iq('sphere', [0.1], **pars), [perfect_target*scale + background])


if __name__ == "__main__":
import logging
Expand Down
Loading

0 comments on commit 8050a44

Please sign in to comment.