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: rectangular approximation for cylinder solid angle #29

Merged
merged 1 commit into from
Dec 11, 2023

Conversation

jokasimr
Copy link
Contributor

@jokasimr jokasimr commented Nov 13, 2023

Fixes #26.
Requires tests before review.

w *= R / sc.norm(w)

# Three corners form a triangle
a = f1c - w
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Triangle coordinates should be relative to origin.

Comment on lines 68 to 72
"""Approximation of the solid angle of a cylinder as seen from `origin`.
The approximation does not take edge caps into account.

Cylinder is approximated by a rectangle cross section parallel
to the axis of the cylinder.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wold not call this an approximation: I think it is actually exact if we want to avoid double counting, given that there are typically many cylinders in a row.

Copy link
Contributor Author

@jokasimr jokasimr Nov 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure but I think it is still approximate.
It is approximate, consider for example if we place a relatively large cylinder close to the 'origin' then the difference will be large.

Found this where they describe an exact analytical formula for the solid angle of a cylinder. It is split into three contributions, one from the body and two from the end-caps. Even if we ignore the contributions from the end-cap surfaces the body contribution is not the same as what we have.

@@ -64,6 +64,41 @@ def solid_angle_rectangular_approximation(
)


def solid_angle_approximation_for_cylinders(cylinders: sc.DataSet, origin: sc.Variable):
Copy link
Member

@SimonHeybrock SimonHeybrock Nov 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have though we need:

  1. Cylinder spec (from NXcylindrical_geometry)
  2. Cylinder positions (from scippnexus.compute_positions). To be added to base cylinder spec.
  3. Sample position (not necessarily exactly the origin)

Also, this probably has to be done after positions calibrations such as beam-center finding. We should use Sciline with domain types to ensure this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

origin here might be a bad name, it's the "point of view" from where the solid angle is observed. In our case I guess we would pass the sample position there.

For this function to work we need that face_center and face_edge and the origin are points in the same (orthonormal) coordinate system. To make that the case I think we will need the cylinder spec + cylinder positions + sample position like you say. But my thinking was that this could be something handled by the caller.


return SolidAngle[RunType](
approximate_solid_angle_for_cylinder_shaped_pixel_of_detector(
# Is there a way to get the positions of the pixels without going through the events?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should assume that the input is a DataArray with a position coord, not the parent data group. This may be containing events or not. For the lowest-level function it may make sense to just take the position.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I assume the data array also has the pixel_shape data or should that enter from a separate provider?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The array does not have pixel shape, since it is stored in a DataGroup, on a higher level, i.e., use a separate provider.

)


def approximate_solid_angle_for_cylinder_shaped_pixel_of_detector(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd remove the whole thing about "approximation". You can briefly mention it in the docstring, but the distance is always large in SANS.

Also, is it really an approximation? Given that the area of a trapezoid is (a+b)*h/2, I'd think it might be exact, given that we use the midpoint to compute the distance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm all for removing the prefix approximate_ if this is the standard approximation typically used in this setting.

Not sure what you mean by it might be exact. The area is not the same as the solid angle. The solid angle is the area projected to the sphere, if the sphere is large and the area is small they are certainly close but it's still an approximation.

That the distance to the midpoint is used as an estimate of the distance to every point on the surface does not change this. The midpoint might be the closest point for all we know.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I was thinking about the perspective effect, but the projection is still another effect that is being ignored.

@jokasimr jokasimr marked this pull request as ready for review November 16, 2023 09:41
@jokasimr jokasimr force-pushed the cylinder-solid-angle branch from 1041691 to 3994ab0 Compare November 16, 2023 09:44
Comment on lines 95 to 98
detector['pixel_shape']['vertices']['vertex', i] for i in range(3)
)
cylinder_axis = (
detector['transform'] * face_2_center - detector['transform'] * face_1_center
Copy link
Member

@SimonHeybrock SimonHeybrock Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I see why you called it DetectorGeometry. In practice this is the parent group of the raw data. I don't think we should expose this detail or any of the names ('pixel_shape' and 'transform') here. Those names can vary, depending on how files are created. This function should takes pixel_shape (or cylinder_geometry) and transform as separate inputs.

@jokasimr jokasimr force-pushed the cylinder-solid-angle branch from 5d1e22d to cc0a972 Compare November 16, 2023 09:49
"""
norm_pp = sc.norm(pixel_position)
norm_ca = sc.norm(cylinder_axis)
cosalpha = sc.norm(sc.cross(pixel_position, cylinder_axis)) / (norm_pp * norm_ca)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this $\sin(\alpha)$? I think you need the dot-product?

Copy link
Contributor Author

@jokasimr jokasimr Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\alpha$ is the angle between the surface normal and pixel_position.
The angle between pixel_position and cylinder_axis is $\theta = \pi/2 - \alpha$.

$||\hat{r} \times \hat{c}|| = \sin(\theta) = \sin(\pi/2 - \alpha)$ $=\cos(\alpha)$

Copy link
Member

@SimonHeybrock SimonHeybrock Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I remember yesterday $\alpha$ was defined as the angle between the vector to the pixel and the same vector without the cylinder_axis components (i.e., projected out). Is this equivalent?

Copy link
Contributor Author

@jokasimr jokasimr Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The definition of solid angle is $\Omega = \int_S \frac{\hat{r} \cdot n}{r^2}\ dS$ where $n$ is the normal of the surface and in our case $S$ is a cylinder.

The cylinder surface is approximated by a rectangle cross section of the cylinder, called $\hat{S}$.

The solid angle of the rectangle $\int_\hat{S} \frac{\hat{r} \cdot n}{r^2}\ dS$ is approximated as $A \frac{\hat{r} \cdot n}{r^2}$ under the assumption that $r$ changes only slightly over the integral. $A$ is the area of $\hat{S}$.

From the above it is clear that we want cosine of the angle between the position and the normal of the surface. I think it is equivalent to what we did yesterday.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which surface normal?

Copy link
Contributor Author

@jokasimr jokasimr Nov 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is the rectangle approximating the cylinder surface selected?

It is selected as the rectangle contained in $S$, parallel to the cylinder axis, with maximal solid angle. This makes $\Omega(\hat{S}) \approx \Omega(S)$ when $||r||$ is large compared to $R$ (the radius of the cylinder). In the below, let $c$ be the cylinder axis, $n$ the rectangle normal, and $\hat{r}$ the unit vector to the center of the cylinder.

  1. $\hat{S}$ is parallel to the axis of the cylinder: $n \cdot c = 0$
  2. $\hat{S}$ is selected to maximize $\hat{r} \cdot n$. Under the constraint in (1) this occurs when $n$, $\hat{r}$ and $c$ all lie in the same plane.

Call the angle between $n$ and $\hat{r}$ $\alpha$, the angle between $c$ and $\hat{r}$ $\theta$.

  • From (1): The angle between $n$ and $c$ is $\pi/2$.
  • From (2): $\alpha + \theta + \pi/2 = \pi \ \iff \theta = \pi/2 - \alpha$

$||\hat{r} \times c|| = \sin(\theta) = \sin(\pi/2 - \alpha)$ $=\cos(\alpha) = \hat{r} \cdot \hat{n} $

Copy link
Contributor Author

@jokasimr jokasimr Nov 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make any performance difference if we used the dot-product instead to compute the same? It would avoid creating an intermediate array of vectors (and have an array of scalars instead)?

Yes maybe there's a better way.
We could do:

From 1 and 2: $\hat{r} = (\hat{r} \cdot n) n + (\hat{r} \cdot c) c$

$\hat{r} \cdot \hat{r} = (\hat{r} \cdot n)^2 + (\hat{r} \cdot c)^2 $
$|\hat{r} \cdot n| = \sqrt{1 - (\hat{r} \cdot c)^2} $

This looks like what you talked about before.

Timed it and the second version is about 3x faster than the first.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of interest, did you check which version is implemented in Mantid?

Can you add the math and explanation as a docstring?

Copy link
Contributor Author

@jokasimr jokasimr Nov 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of interest, did you check which version is implemented in Mantid?

I didn't understand what they did there before. But now I think I see what they do.
First, they work with the non-normalized position vector $r$ instead of $\hat{r}$.
They also project $r$ on the plane orthogonal to $c$:
$p := r - (r \cdot c)c = (r\cdot n) n$

But then they do slightly different, they return
$\frac{p \cdot r}{||r|| \ ||p||} = \frac{(r \cdot n)^2}{||r||^2cos(\alpha)} = |\hat{r}\cdot n| = cos(\alpha)$

In terms of efficiency this version and the one we have now are about the same.
It looks here like they have two scalar products but they don't because the projection on the plane orthogonal to $c$ is just setting the y-component (or x-component) to 0 in their implementation. They can do that because it is assumed that the detector axis is either horizontal or vertical.

There's another difference in their implementation

    const V3D scaleFactor = m_componentInfo.scaleFactor(index);
    const double scaledPixelArea = m_pixelArea * scaleFactor[0] * scaleFactor[1];
    return scaledPixelArea * cosAlpha / (l2 * l2);

This looks like what we have except the multiplications by the two scale-factors.
I don't know what the scaleFactors represent.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can ignore scaleFactor: This comes from the IDF description of the pixel shape, which may define a unit-cylinder, which is then scaled to the actual size. In NeXus there is not such scale, the actual size is stored directly.

Comment on lines 125 to 129
norm_pp = sc.norm(pixel_position)
norm_ca = sc.norm(cylinder_axis)
cosalpha = sc.sqrt(1 - sc.dot(pixel_position, cylinder_axis) / (norm_pp * norm_ca))

return 2 * radius * length * cosalpha / norm_pp**2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how much this matters here, but not that you could make this faster by avoiding a lot of large intermediate results, using, e.g., += and *=. It generally get harder to read though.

There are also some easy gains to be made, e.g., by putting parenthesis (and/or reordering) in the last line, so ensure we first combine all the 0-D variables, to ensure we have to create fewer pixel-dependent intermediate results.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to group the 0-D variables, I'll fix that. But I don't think the performance improvement from using inplace operations will be worth the hit to readability in this case.

"""Geometry of the detector from description in nexus file."""


class SampleReferenceTransform(sciline.Scope[RunType, sc.Variable], sc.Variable):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think people will find this name confusing, since many instruments may use "reference samples" to compare results to known results from, e.g., other facilities/instruments or.

Maybe LabFrameTransform?

@jokasimr
Copy link
Contributor Author

jokasimr commented Nov 30, 2023

Test to compare solid angle computation in this PR with Mantid.

import scipp as sc
import scippneutron as scn
from mantid.simpleapi import Load, SolidAngle, SetInstrumentParameter

ws = Load('SANS2D00063091.nxs')
radius = 0.00405 * sc.Unit('m')
length = 0.002033984375 * sc.Unit('m')

SetInstrumentParameter(ws, ParameterName='x-pixel-size', ParameterType='Number', Value=str((2 * radius).to(unit='mm').value))
SetInstrumentParameter(ws, ParameterName='y-pixel-size', ParameterType='Number', Value=str(length.to(unit='mm').value))

outWs = SolidAngle(ws, method='HorizontalTube')
dg = scn.from_mantid(outWs)

# Horizontal tubes
cylinder_axis = sc.scalar([1, 0, 0], dtype=sc.DType.vector3, unit='m')
pixel_position = dg['data'].coords['position'] - dg['data'].coords['sample_position'] 

# Compute solid angle like in the implementation
norm_pp = sc.norm(pixel_position)
norm_ca = sc.norm(cylinder_axis)
cosalpha = sc.sqrt(
    1 - (sc.dot(pixel_position, cylinder_axis) / (norm_pp * norm_ca)) ** 2
)
solid_angle = (2 * radius * length) * cosalpha / norm_pp**2

# Get solid angle results from mantid
mantid_solid_angle = dg['data'].data['tof', 0]

# Remove nonexisting detector panels
solid_angle = solid_angle[mantid_solid_angle != sc.scalar(0)]
mantid_solid_angle = mantid_solid_angle[mantid_solid_angle != sc.scalar(0)]

assert sc.allclose(solid_angle, mantid_solid_angle)

I'll add part of this data as a reference to unit test against.

@jokasimr jokasimr force-pushed the cylinder-solid-angle branch 2 times, most recently from 44d1988 to 41db7c9 Compare November 30, 2023 15:10
@nvaytet
Copy link
Member

nvaytet commented Dec 1, 2023

See also mantidproject/mantid#36493

@SimonHeybrock
Copy link
Member

Test to compare solid angle computation in this PR with Mantid.

import scipp as sc
import scippneutron as scn
from mantid.simpleapi import Load, SolidAngle, SetInstrumentParameter

ws = Load('SANS2D00063091.nxs')
radius = 0.00405 * sc.Unit('m')
length = 0.002033984375 * sc.Unit('m')

SetInstrumentParameter(ws, ParameterName='x-pixel-size', ParameterType='Number', Value=str((2 * radius).to(unit='mm').value))
SetInstrumentParameter(ws, ParameterName='y-pixel-size', ParameterType='Number', Value=str(length.to(unit='mm').value))

outWs = SolidAngle(ws, method='HorizontalTube')
dg = scn.from_mantid(outWs)

# Horizontal tubes
cylinder_axis = sc.scalar([1, 0, 0], dtype=sc.DType.vector3, unit='m')
pixel_position = dg['data'].coords['position'] - dg['data'].coords['sample_position'] 

# Compute solid angle like in the implementation
norm_pp = sc.norm(pixel_position)
norm_ca = sc.norm(cylinder_axis)
cosalpha = sc.sqrt(
    1 - (sc.dot(pixel_position, cylinder_axis) / (norm_pp * norm_ca)) ** 2
)
solid_angle = (2 * radius * length) * cosalpha / norm_pp**2

# Get solid angle results from mantid
mantid_solid_angle = dg['data'].data['tof', 0]

# Remove nonexisting detector panels
solid_angle = solid_angle[mantid_solid_angle != sc.scalar(0)]
mantid_solid_angle = mantid_solid_angle[mantid_solid_angle != sc.scalar(0)]

assert sc.allclose(solid_angle, mantid_solid_angle)

I'll add part of this data as a reference to unit test against.

It would be useful to preserve this code as a unit test. Can you make one that runs only when Mantid can be imported (i.e., it would not run on CI, but we can run it locally).

@jokasimr
Copy link
Contributor Author

jokasimr commented Dec 4, 2023

It would be useful to preserve this code as a unit test. Can you make one that runs only when Mantid can be imported (i.e., it would not run on CI, but we can run it locally).

Specifically what part of it do you want preserved as a unit test? I'm asking because there already exists a unit test here that makes the comparison with the Mantid reference.

It might be useful to preserve the code that created the reference though.

@SimonHeybrock
Copy link
Member

It would be useful to preserve this code as a unit test. Can you make one that runs only when Mantid can be imported (i.e., it would not run on CI, but we can run it locally).

Specifically what part of it do you want preserved as a unit test? I'm asking because there already exists a unit test here that makes the comparison with the Mantid reference.

That does not show how Mantid was used to compute the solid angle (and as we have seen there are multiple ways). So what I am referring to is the actual Python code that calls Mantid that you have in your code snippet.

Comment on lines 91 to 92
da_old = sc.io.load_hdf5(filename=fname)
assert sc.identical(da, da_old)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about comparing to the actual computation, as you have done in the other test? You can move the common code into a helper function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what you mean. This test verifies the reference has not changed. The other test verifies our implementation (the actual computation) gives the same result as the reference.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it depends on your point of view. Given the history of how you arrived at this point it makes sense to compare the Mantid result to a reference file.

But I feel that in general comparing "result of software A" to "result of software B" is a "better" test. It is easier to understand out of context, and is less likely to break. For example, what if we later make some changes to the code in esssans, change how the test of solid angle works.... but forget that we still have the old reference file. You have now a test that claims that everything is fine, but in fact it is not! This problem would not arise if you directly compared the results of the two implementations, without relying on some files that were written at some point in the past.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean. But if we compare with mantid directly instead of comparing with the reference file then we will only be able to run the test locally if mantid and scippneutron is installed in the same environment. I think that test will be run very rarely. With the reference file the test can be run in CI, making it much more likely we catch a regression.

How about this: In the test that recreates the mantid file we compare the results from both softwares directly and with the reference file, if any of them disagrees an error is reported. Maybe that was what you had in mind already?

Copy link
Member

@SimonHeybrock SimonHeybrock Dec 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I had in mind was:

  1. Your existing test, comparing the esssans result to the reference file.
  2. A test that compares the esssans result to the Mantid result.

If Mantid is missing, the second test will not run.

solid_angle = normalization.solid_angle(
da, pixel_shape=pixel_shape, transform=transform
)
assert sc.allclose(solid_angle, mantid_solid_angle)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you set atol and rtol explicitly? The defaults may hide some relevant differences. In particular the default atol may be way too large, since the solid angle results are quite small.

Comment on lines 108 to 118
'vertices': sc.array(
dims=['vertex'],
values=[
[0, 0, 0],
[R, 0, 0],
[0, L, 0],
],
dtype=sc.DType.vector3,
unit='m',
)
}
Copy link
Member

@SimonHeybrock SimonHeybrock Dec 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may be looking for https://scipp.github.io/generated/functions/scipp.vectors.html, which simplifies the creation.

Copy link
Member

@SimonHeybrock SimonHeybrock Dec 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the file is small, I am not sure at all sure that adding test data to the repo is a good idea. Can we get it using pooch, as done in other projects?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it not a good idea? Seems more complicated to put it somewhere else and fetch it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it sets a precedence. How many "small" files is it ok to add? What is a "small" file?

Basically, I am worried about the future. Yes, it is more complicated now, but we will anyway need the setup for other files. And it already exists, basically, so there is not much extra work: https://github.com/scipp/esssans/blob/main/src/esssans/data.py.

Copy link
Contributor Author

@jokasimr jokasimr Dec 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for files used in tests having them in VC makes sense. Putting it elsewhere means that if a reference file changes

  1. we can not go back and see when and how a file/test changed,
  2. it breaks old versions of the repo unless the old versions of the files are also kept around.

I agree the line between too-large and small is fuzzy, but if we can add many similar sized files without significantly impacting users of the repo then IMO it is small enough.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but if we can add many similar sized files without significantly impacting users of the repo then IMO it is small enough.

Can we? How do you know it is not impacting users of the repo? Having binary files in Git is generally not a great idea, is it?

Copy link
Contributor Author

@jokasimr jokasimr Dec 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we? How do you know it is not impacting users of the repo?

If the files are small compared to the size of the repository I think it is fair to say that it doesn't impact users.

Having binary files in Git is generally not a great idea, is it?

It's not good if the files are updated frequently. Is it also bad in other circumstances?

I'm of course open to storing the reference file in an external location if you think that is the better option.
But for the reasons I mentioned above that brings its own issues.

If you're concerned about potentially increasing the size of the repository (in case we update the reference file frequently, I don't think we will) I would suggest reducing the file size instead of storing it separately.
Including 4x fewer pixels in each dimension would reduce the file size considerably but I think it would still constitute a good test.

@@ -64,6 +66,92 @@ def solid_angle_rectangular_approximation(
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the old function should be removed.

and :math:`\hat{r}` is the normalized pixel position vector.
"""
norm_pp = sc.norm(pixel_position)
norm_ca = sc.norm(cylinder_axis)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this the same as length? Why does the caller have to provide that?

Copy link
Contributor Author

@jokasimr jokasimr Dec 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The direction of cylinder_axis is required to compute $cos (\alpha)$. Yes I guess it's length ought to be 1 but normalizing it makes sure it can't even become an issue if someone forgets to make it length 1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But norm_ca is the same as length?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I think I understand the function interface now. I found it a bit surprising, but you can leave it.

Comment on lines 87 to 86
assert sc.allclose(
solid_angle.data * 3,
normalization.solid_angle_rectangular_approximation(da).data,
da.data['tof', 0], solid_angle, atol=1e-10 * sc.Unit('dimensionless')
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So how large are the absolute and relative differences? How large are the values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The values are approximately 1e-6 and the maximum of the absolute difference is machine precision (the number I get is ~1e-22).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you thus make atol and rtol as low as possible? This would make it clear that the results are really "the same".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? Is a relative error of 1e-4 not sufficiently small? If we make the tolerances too low the test might break from a small change that doesn't meaningfully influence the result. The default rtol in allclose is 1e-5. So this is already about the same accuracy we have in every other place where allclose is used.

Comment on lines +149 to +153
# Coordinates in pixel-local coordinate system
# Bottom face center
[0, 0, 0],
# Bottom face edge
[R, 0, 0],
# Top face center
[0, L, 0],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought Sans2d has horizontal tubes? This looks vertical to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the transform turns them horizontal.

da.data['tof', 0], solid_angle, atol=1e-10 * sc.Unit('dimensionless')
da.data['tof', 0], solid_angle, atol=1e-11 * sc.Unit('dimensionless')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to also set rtol (e.g., to zero)

Comment on lines +142 to +141
R = 0.00405
L = 0.002033984375
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to remove to old values above (around line 40)

Copy link
Member

@SimonHeybrock SimonHeybrock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work 👍

@jokasimr jokasimr force-pushed the cylinder-solid-angle branch from ef4c3dd to 9d96cef Compare December 11, 2023 09:01
@jokasimr jokasimr merged commit a04ac17 into main Dec 11, 2023
3 checks passed
@jokasimr jokasimr deleted the cylinder-solid-angle branch December 11, 2023 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Solid angle for cylinders
3 participants