Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update docstrings and tests, introduce orcid argument #154

Merged
merged 6 commits into from
Jan 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions news/docstring-tests.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* Functionality in `load_user_info` to enable user to enter an ORCID.

**Changed:**

* All function docstrings and tests to be more informative, incorporating new ORCID function and improving overall clarity.

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* <news item>

**Security:**

* <news item>
224 changes: 96 additions & 128 deletions src/diffpy/labpdfproc/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
TTH_GRID[-1] = 180.00
CVE_METHODS = ["brute_force", "polynomial_interpolation"]

# pre-computed datasets for polynomial interpolation (fast calculation)
# Pre-computed datasets for polynomial interpolation (fast calculation)
MUD_LIST = [0.5, 1, 2, 3, 4, 5, 6]
CWD = Path(__file__).parent.resolve()
MULS = np.loadtxt(CWD / "data" / "inverse_cve.xy")
Expand All @@ -32,91 +32,43 @@ def __init__(self, radius=1, n_points_on_diameter=N_POINTS_ON_DIAMETER, mu=None)
self._get_grid_points()

def _get_grid_points(self):
"""
given a radius and a grid size, return a grid of points to uniformly sample that circle
"""
"""Given a radius and a grid size, return a grid of points to uniformly sample that circle."""
xs = np.linspace(-self.radius, self.radius, self.npoints)
ys = np.linspace(-self.radius, self.radius, self.npoints)
self.grid = {(x, y) for x in xs for y in ys if x**2 + y**2 <= self.radius**2}
self.total_points_in_grid = len(self.grid)

def set_distances_at_angle(self, angle):
sbillinge marked this conversation as resolved.
Show resolved Hide resolved
"""
given an angle, set the distances from the grid points to the entry and exit coordinates

Parameters
----------
angle float
the angle in degrees

Returns
-------
the list of distances containing total distance, primary distance and secondary distance

"""
self.primary_distances, self.secondary_distances, self.distances = [], [], []
for coord in self.grid:
distance, primary, secondary = self.get_path_length(coord, angle)
self.distances.append(distance)
self.primary_distances.append(primary)
self.secondary_distances.append(secondary)

def set_muls_at_angle(self, angle):
"""
compute muls = exp(-mu*distance) for a given angle

Parameters
----------
angle float
the angle in degrees

Returns
-------
an array of floats containing the muls corresponding to each angle

"""
mu = self.mu
self.muls = []
if len(self.distances) == 0:
self.set_distances_at_angle(angle)
for distance in self.distances:
self.muls.append(np.exp(-mu * distance))

def _get_entry_exit_coordinates(self, coordinate, angle):
"""
get the coordinates where the beam enters and leaves the circle for a given angle and grid point

Parameters
----------
grid_point tuple of floats
the coordinates of the grid point

angle float
the angle in degrees
"""Get the coordinates where the beam enters and leaves the circle for a given angle and grid point.

radius float
the radius of the circle in units of inverse mu

it is calculated in the following way:
It is calculated in the following way:
For the entry coordinate, the y-component will be the y of the grid point and the x-component will be minus
the value of x on the circle at the height of this y.

For the exit coordinate:
Find the line y = ax + b that passes through grid_point at angle angle
The circle is x^2 + y^2 = r^2
Find the line y = ax + b that passes through grid_point at angle.
The circle is x^2 + y^2 = r^2.
The exit point is where these are simultaneous equations
x^2 + y^2 = r^2 & y = ax + b
x^2 + (ax+b)^2 = r^2
=> x^2 + a^2x^2 + 2abx + b^2 - r^2 = 0
=> (1+a^2) x^2 + 2abx + (b^2 - r^2) = 0
to find x_exit we find the roots of these equations and pick the root that is above y-grid
then we get y_exit from y_exit = a*x_exit + b
then we get y_exit from y_exit = a*x_exit + b.

Parameters
----------
coordinate : tuple of floats
The coordinates of the grid point.

angle : float
The angle in degrees.

Returns
-------
(1) the coordinate of the entry point and (2) of the exit point of a beam entering horizontally
impinging on a coordinate point that lies in the circle and then exiting at some angle, angle.

(entry_point, exit_point): tuple of floats
(1) The coordinate of the entry point and (2) of the exit point of a beam entering horizontally
impinging on a coordinate point that lies in the circle and then exiting at some angle, angle.
"""
epsilon = 1e-7 # precision close to 90
angle = math.radians(angle)
Expand All @@ -140,28 +92,22 @@ def _get_entry_exit_coordinates(self, coordinate, angle):

return entry_point, exit_point

def get_path_length(self, grid_point, angle):
"""
return the path length

This is the pathlength of a horizontal line entering the circle at the
same height to the grid point then exiting at angle angle
def _get_path_length(self, grid_point, angle):
sbillinge marked this conversation as resolved.
Show resolved Hide resolved
"""Return the path length of a horizontal line entering the circle at the
same height to the grid point then exiting at angle.

Parameters
----------
grid_point double of floats
the coordinate inside the circle
grid_point : double of floats
The coordinate inside the circle.

angle float
the angle of the output beam

radius
the radius of the circle
angle : float
The angle of the output beam in degrees.

Returns
-------
floats total distance, primary distance and secondary distance

(total distance, primary distance, secondary distance): tuple of floats
The tuple containing three floats, which are the total distance, entry distance and exit distance.
"""

# move angle a tad above zero if it is zero to avoid it having the wrong sign due to some rounding error
Expand All @@ -174,13 +120,41 @@ def get_path_length(self, grid_point, angle):
total_distance = primary_distance + secondary_distance
return total_distance, primary_distance, secondary_distance

def set_distances_at_angle(self, angle):
"""Given an angle, set the distances from the grid points to the entry and exit coordinates.

def _cve_brute_force(diffraction_data, mud):
"""
compute cve for the given mud on a global grid using the brute-force method
assume mu=mud/2, given that the same mu*D yields the same cve and D/2=1
"""
Parameters
----------
angle : float
The angle of the output beam in degrees.
"""
self.primary_distances, self.secondary_distances, self.distances = [], [], []
for coord in self.grid:
distance, primary, secondary = self._get_path_length(coord, angle)
self.distances.append(distance)
self.primary_distances.append(primary)
self.secondary_distances.append(secondary)

def set_muls_at_angle(self, angle):
"""Compute muls = exp(-mu*distance) for a given angle.

Parameters
----------
angle : float
The angle of the output beam in degrees.
"""
mu = self.mu
self.muls = []
if len(self.distances) == 0:
self.set_distances_at_angle(angle)
for distance in self.distances:
self.muls.append(np.exp(-mu * distance))


def _cve_brute_force(input_pattern, mud):
"""Compute cve for the given mud on a global grid using the brute-force method.
Assume mu=mud/2, given that the same mu*D yields the same cve and D/2=1.
"""
mu_sample_invmm = mud / 2
abs_correction = Gridded_circle(mu=mu_sample_invmm)
distances, muls = [], []
Expand All @@ -197,19 +171,18 @@ def _cve_brute_force(diffraction_data, mud):
xarray=TTH_GRID,
yarray=cve,
xtype="tth",
wavelength=diffraction_data.wavelength,
wavelength=input_pattern.wavelength,
sbillinge marked this conversation as resolved.
Show resolved Hide resolved
scat_quantity="cve",
name=f"absorption correction, cve, for {diffraction_data.name}",
metadata=diffraction_data.metadata,
name=f"absorption correction, cve, for {input_pattern.name}",
metadata=input_pattern.metadata,
)
return cve_do


def _cve_polynomial_interpolation(diffraction_data, mud):
"""
compute cve using polynomial interpolation method, raise an error if mu*D is out of the range (0.5 to 6)
def _cve_polynomial_interpolation(input_pattern, mud):
"""Compute cve using polynomial interpolation method,
raise an error if the mu*D value is out of the range (0.5 to 6).
"""

if mud > 6 or mud < 0.5:
raise ValueError(
f"mu*D is out of the acceptable range (0.5 to 6) for polynomial interpolation. "
Expand All @@ -225,18 +198,16 @@ def _cve_polynomial_interpolation(diffraction_data, mud):
xarray=TTH_GRID,
yarray=cve,
xtype="tth",
wavelength=diffraction_data.wavelength,
wavelength=input_pattern.wavelength,
scat_quantity="cve",
name=f"absorption correction, cve, for {diffraction_data.name}",
metadata=diffraction_data.metadata,
name=f"absorption correction, cve, for {input_pattern.name}",
metadata=input_pattern.metadata,
)
return cve_do


def _cve_method(method):
"""
retrieve the cve computation function for the given method
"""
"""Retrieve the cve computation function for the given method."""
methods = {
"brute_force": _cve_brute_force,
"polynomial_interpolation": _cve_polynomial_interpolation,
Expand All @@ -246,60 +217,57 @@ def _cve_method(method):
return methods[method]


def compute_cve(diffraction_data, mud, method="polynomial_interpolation", xtype="tth"):
f"""
compute and interpolate the cve for the given diffraction data and mud using the selected method
def compute_cve(input_pattern, mud, method="polynomial_interpolation", xtype="tth"):
f"""Compute and interpolate the cve for the given input diffraction data and mu*D using the selected method.

Parameters
----------
diffraction_data Diffraction_object
the diffraction pattern
mud float
the mu*D of the diffraction object, where D is the diameter of the circle
xtype str
the quantity on the independent variable axis, allowed values are {*XQUANTITIES, }
method str
the method used to calculate cve, must be one of {*CVE_METHODS, }
input_pattern : DiffractionObject
The input diffraction object to which the cve will be applied.
mud : float
The mu*D value of the diffraction object, where D is the diameter of the circle.
xtype : str
The quantity on the independent variable axis, allowed values are {*XQUANTITIES, }.
method : str
The method used to calculate cve, must be one of {*CVE_METHODS, }.

Returns
-------
the diffraction object with cve curves
cve_do: DiffractionObject
The diffraction object that contains the cve to be applied.
"""

cve_function = _cve_method(method)
cve_do_on_global_grid = cve_function(diffraction_data, mud)
orig_grid = diffraction_data.on_xtype(xtype)[0]
cve_do_on_global_grid = cve_function(input_pattern, mud)
orig_grid = input_pattern.on_xtype(xtype)[0]
global_xtype = cve_do_on_global_grid.on_xtype(xtype)[0]
cve_on_global_xtype = cve_do_on_global_grid.on_xtype(xtype)[1]
newcve = np.interp(orig_grid, global_xtype, cve_on_global_xtype)
cve_do = DiffractionObject(
xarray=orig_grid,
yarray=newcve,
xtype=xtype,
wavelength=diffraction_data.wavelength,
wavelength=input_pattern.wavelength,
scat_quantity="cve",
name=f"absorption correction, cve, for {diffraction_data.name}",
metadata=diffraction_data.metadata,
name=f"absorption correction, cve, for {input_pattern.name}",
metadata=input_pattern.metadata,
)
return cve_do


def apply_corr(diffraction_pattern, absorption_correction):
"""
Apply absorption correction to the given diffraction object modo with the correction diffraction object abdo
def apply_corr(input_pattern, absorption_correction):
"""Apply absorption correction to the given diffraction object with the correction diffraction object.

Parameters
----------
diffraction_pattern Diffraction_object
the input diffraction object to which the cve will be applied
absorption_correction Diffraction_object
the diffraction object that contains the cve to be applied
input_pattern : DiffractionObject
The input diffraction object to which the cve will be applied.
absorption_correction : DiffractionObject
The diffraction object that contains the cve to be applied.

Returns
-------
a corrected diffraction object with the correction applied through multiplication

corrected_pattern: DiffractionObject
The corrected diffraction object with the correction applied through multiplication.
"""

corrected_pattern = diffraction_pattern * absorption_correction
corrected_pattern = input_pattern * absorption_correction
return corrected_pattern
8 changes: 8 additions & 0 deletions src/diffpy/labpdfproc/labpdfprocapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ def define_arguments():
),
"default": None,
},
{
"name": ["--orcid"],
sbillinge marked this conversation as resolved.
Show resolved Hide resolved
"help": (
"ORCID will be loaded from config files. Specify here "
"only if you want to override that behavior at runtime. "
),
"default": None,
},
{
"name": ["-z", "--z-scan-file"],
"help": "Path to the z-scan file to be loaded to determine the mu*D value",
Expand Down
Loading
Loading