Bragg prediction: derive the lattice walk from the cell, and expose it in the API
Build Packages / build:viewer-tgz:cpu (push) Successful in 7m15s
Build Packages / build:viewer-tgz:cuda (push) Successful in 8m42s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 13m47s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m1s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 14m11s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 14m19s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 14m31s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 12m39s
Build Packages / build:rpm (rocky8) (push) Successful in 11m41s
Build Packages / XDS test (durin plugin) (push) Successful in 7m52s
Build Packages / Generate python client (push) Successful in 34s
Build Packages / Build documentation (push) Successful in 1m3s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (ubuntu2404) (push) Successful in 12m10s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m0s
Build Packages / build:rpm (rocky9) (push) Successful in 13m39s
Build Packages / DIALS test (push) Successful in 14m29s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m33s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m25s
Build Packages / Unit tests (push) Successful in 1h36m21s
Build Packages / build:windows:nocuda (push) Canceled after 0s
Build Packages / build:windows:cuda (push) Canceled after 0s
Build Packages / build:viewer-tgz:cpu (push) Successful in 7m15s
Build Packages / build:viewer-tgz:cuda (push) Successful in 8m42s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 13m47s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m1s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 14m11s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 14m19s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 14m31s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 12m39s
Build Packages / build:rpm (rocky8) (push) Successful in 11m41s
Build Packages / XDS test (durin plugin) (push) Successful in 7m52s
Build Packages / Generate python client (push) Successful in 34s
Build Packages / Build documentation (push) Successful in 1m3s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (ubuntu2404) (push) Successful in 12m10s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m0s
Build Packages / build:rpm (rocky9) (push) Successful in 13m39s
Build Packages / DIALS test (push) Successful in 14m29s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m33s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m25s
Build Packages / Unit tests (push) Successful in 1h36m21s
Build Packages / build:windows:nocuda (push) Canceled after 0s
Build Packages / build:windows:cuda (push) Canceled after 0s
Follow-up to making max_hkl a setting: it is now an optional, and unset means "take it from this crystal". The predictor keeps only |q| <= 1/d_min and h = a.q for the real-space axis a, so |h| <= a/d_min exactly - and likewise |k| <= b/d_min and |l| <= c/d_min. max(a,b,c)/d_min therefore bounds all three at once: nothing that could be predicted lies outside it, and nothing inside it is reached by a shorter axis. It applies to rotation and stills alike, both going through the one place the prediction settings are built. Offline (rugnux, viewer) the default is unset, so every crystal gets its own range; --max-hkl overrides it. Online the broker holds a concrete number, because the cost is the cube of it per image and a live acquisition should not have its frame rate decided by whichever sample is mounted: max_hkl joins bragg_integration_settings in the OpenAPI with a default of 100, so an omitted field arrives as that default (the generated model carries it) rather than as "derive it", and the frontend exposes it next to the integration model. Measured against a fixed 100 on six rotation crystals: three are bit-identical, two were being truncated and recover 419k and 5.8k observations with the high-shell CC1/2 going 15.1 -> 25.8% and 52.1 -> 55.3%, and the space group is unchanged 6/6. It reproduces a fixed 200 exactly, which is the bound being tight rather than merely safe. The sixth is worth recording: a 149/83/226 A cell derives 227, and because a single scalar has to cover the longest axis the cube is ~16x what a per-axis box would be - 22% wall clock, for a net 22 observations out of 364k (the per-frame 65536-reflection cap re-selects at the margin when more candidates are offered) and identical CC1/2, ISa and space group. Per-axis limits would remove that; the predictors already map a thread index to h, k and l separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1073,6 +1073,11 @@ BraggIntegrationSettings Convert(const org::openapitools::server::model::Bragg_i
|
||||
default:
|
||||
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Unknown integration model");
|
||||
}
|
||||
// Always a concrete number online, never "derive it from the crystal": the generated model holds
|
||||
// the schema's default when the request omits the field, so an absent max_hkl arrives here as that
|
||||
// default rather than as an absent value. Deriving per crystal would make a live acquisition's
|
||||
// per-image cost depend on whichever sample is mounted.
|
||||
ret.MaxHKL(input.getMaxHkl());
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1091,6 +1096,8 @@ org::openapitools::server::model::Bragg_integration_settings Convert(const Bragg
|
||||
break;
|
||||
}
|
||||
ret.setIntegrationModel(tmp);
|
||||
if (const auto max_hkl = input.GetMaxHKL())
|
||||
ret.setMaxHkl(*max_hkl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace org::openapitools::server::model
|
||||
|
||||
Bragg_integration_settings::Bragg_integration_settings()
|
||||
{
|
||||
m_Max_hkl = 100;
|
||||
m_Max_hklIsSet = false;
|
||||
|
||||
}
|
||||
|
||||
@@ -43,7 +45,26 @@ bool Bragg_integration_settings::validate(std::stringstream& msg, const std::str
|
||||
bool success = true;
|
||||
const std::string _pathPrefix = pathPrefix.empty() ? "Bragg_integration_settings" : pathPrefix;
|
||||
|
||||
|
||||
if (maxHklIsSet())
|
||||
{
|
||||
const int32_t& value = m_Max_hkl;
|
||||
const std::string currentValuePath = _pathPrefix + ".maxHkl";
|
||||
|
||||
|
||||
if (value < 1)
|
||||
{
|
||||
success = false;
|
||||
msg << currentValuePath << ": must be greater than or equal to 1;";
|
||||
}
|
||||
if (value > 511)
|
||||
{
|
||||
success = false;
|
||||
msg << currentValuePath << ": must be less than or equal to 511;";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -53,8 +74,11 @@ bool Bragg_integration_settings::operator==(const Bragg_integration_settings& rh
|
||||
|
||||
|
||||
(getIntegrationModel() == rhs.getIntegrationModel())
|
||||
&&
|
||||
|
||||
|
||||
((!maxHklIsSet() && !rhs.maxHklIsSet()) || (maxHklIsSet() && rhs.maxHklIsSet() && getMaxHkl() == rhs.getMaxHkl()))
|
||||
|
||||
;
|
||||
}
|
||||
|
||||
@@ -67,12 +91,19 @@ void to_json(nlohmann::json& j, const Bragg_integration_settings& o)
|
||||
{
|
||||
j = nlohmann::json::object();
|
||||
j["integration_model"] = o.m_Integration_model;
|
||||
if(o.maxHklIsSet())
|
||||
j["max_hkl"] = o.m_Max_hkl;
|
||||
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, Bragg_integration_settings& o)
|
||||
{
|
||||
j.at("integration_model").get_to(o.m_Integration_model);
|
||||
if(j.find("max_hkl") != j.end())
|
||||
{
|
||||
j.at("max_hkl").get_to(o.m_Max_hkl);
|
||||
o.m_Max_hklIsSet = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -84,6 +115,23 @@ void Bragg_integration_settings::setIntegrationModel(org::openapitools::server::
|
||||
{
|
||||
m_Integration_model = value;
|
||||
}
|
||||
int32_t Bragg_integration_settings::getMaxHkl() const
|
||||
{
|
||||
return m_Max_hkl;
|
||||
}
|
||||
void Bragg_integration_settings::setMaxHkl(int32_t const value)
|
||||
{
|
||||
m_Max_hkl = value;
|
||||
m_Max_hklIsSet = true;
|
||||
}
|
||||
bool Bragg_integration_settings::maxHklIsSet() const
|
||||
{
|
||||
return m_Max_hklIsSet;
|
||||
}
|
||||
void Bragg_integration_settings::unsetMax_hkl()
|
||||
{
|
||||
m_Max_hklIsSet = false;
|
||||
}
|
||||
|
||||
|
||||
} // namespace org::openapitools::server::model
|
||||
|
||||
@@ -63,12 +63,21 @@ public:
|
||||
/// </summary>
|
||||
org::openapitools::server::model::Integration_model getIntegrationModel() const;
|
||||
void setIntegrationModel(org::openapitools::server::model::Integration_model const& value);
|
||||
/// <summary>
|
||||
/// How far the Bragg predictor walks the lattice: reflections with |h|,|k|,|l| above this are never predicted. An axis is truncated once a/d_min exceeds it, so a long axis - or a short one taken to high resolution - loses its outermost reflections. The cost grows as the cube (2n+1)^3 of candidates per image, which is why online keeps a fixed, predictable value instead of taking it from whichever crystal is mounted. Omitting the field selects the default above; it is never interpreted as \"choose per crystal\". The offline tools (rugnux, viewer) do derive it from the refined cell when it is left unset there, but that is their own default and is not reachable through this API.
|
||||
/// </summary>
|
||||
int32_t getMaxHkl() const;
|
||||
void setMaxHkl(int32_t const value);
|
||||
bool maxHklIsSet() const;
|
||||
void unsetMax_hkl();
|
||||
|
||||
friend void to_json(nlohmann::json& j, const Bragg_integration_settings& o);
|
||||
friend void from_json(const nlohmann::json& j, Bragg_integration_settings& o);
|
||||
protected:
|
||||
org::openapitools::server::model::Integration_model m_Integration_model;
|
||||
|
||||
int32_t m_Max_hkl;
|
||||
bool m_Max_hklIsSet;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -2348,6 +2348,21 @@ components:
|
||||
properties:
|
||||
integration_model:
|
||||
$ref: '#/components/schemas/integration_model'
|
||||
max_hkl:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 511
|
||||
default: 100
|
||||
description: |
|
||||
How far the Bragg predictor walks the lattice: reflections with |h|,|k|,|l| above this are
|
||||
never predicted. An axis is truncated once a/d_min exceeds it, so a long axis - or a short
|
||||
one taken to high resolution - loses its outermost reflections. The cost grows as the cube
|
||||
(2n+1)^3 of candidates per image, which is why online keeps a fixed, predictable value
|
||||
instead of taking it from whichever crystal is mounted.
|
||||
|
||||
Omitting the field selects the default above; it is never interpreted as "choose per
|
||||
crystal". The offline tools (rugnux, viewer) do derive it from the refined cell when it is
|
||||
left unset there, but that is their own default and is not reachable through this API.
|
||||
jfjoch_settings:
|
||||
type: object
|
||||
required:
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -109,15 +109,17 @@ float BraggIntegrationSettings::GetBackgroundTrimFraction() const {
|
||||
return bkg_trim_fraction;
|
||||
}
|
||||
|
||||
BraggIntegrationSettings &BraggIntegrationSettings::MaxHKL(int input) {
|
||||
check_min("Maximum hkl index", input, 1);
|
||||
// The GPU predictor launches one thread per candidate, so the cost is (2n+1)^3: 511 is 1.1e9
|
||||
// candidates per frame, already far past the point where prediction dominates a run.
|
||||
check_max("Maximum hkl index", input, 511);
|
||||
BraggIntegrationSettings &BraggIntegrationSettings::MaxHKL(std::optional<int> input) {
|
||||
if (input) {
|
||||
check_min("Maximum hkl index", *input, 1);
|
||||
// The GPU predictor launches one thread per candidate, so the cost is (2n+1)^3: 511 is 1.1e9
|
||||
// candidates per frame, already far past the point where prediction dominates a run.
|
||||
check_max("Maximum hkl index", *input, 511);
|
||||
}
|
||||
max_hkl = input;
|
||||
return *this;
|
||||
}
|
||||
|
||||
int BraggIntegrationSettings::GetMaxHKL() const {
|
||||
std::optional<int> BraggIntegrationSettings::GetMaxHKL() const {
|
||||
return max_hkl;
|
||||
}
|
||||
|
||||
@@ -29,10 +29,12 @@ class BraggIntegrationSettings {
|
||||
float bkg_trim_fraction = 0.10f;
|
||||
// Half-width of the hkl cube the predictor walks: every reflection with |h|,|k|,|l| <= this is
|
||||
// tested against the Ewald sphere, and nothing outside it can ever be predicted. An axis is
|
||||
// truncated once a/d_min exceeds this, so 100 covers a 150 A axis at 1.5 A but not at 1.0 A. The
|
||||
// cost is the cube (2n+1)^3 of candidates on the GPU, which is why it is not simply set high:
|
||||
// raising it lengthens every frame's prediction whether or not the cell needs the room.
|
||||
int max_hkl = 100;
|
||||
// truncated once a/d_min exceeds this, and the GPU cost is the cube (2n+1)^3 of candidates, so
|
||||
// neither a small nor a large fixed value is right for every crystal.
|
||||
//
|
||||
// Unset (the default) means "take it from the refined cell", which is exact: the predictor keeps
|
||||
// only |q| <= 1/d_min and h = a.q, so no reflection can have |h| > a/d_min. See MaxHKLForCell.
|
||||
std::optional<int> max_hkl;
|
||||
|
||||
public:
|
||||
BraggIntegrationSettings& R1(float input);
|
||||
@@ -42,7 +44,7 @@ public:
|
||||
BraggIntegrationSettings& FixedProfileRadius_recipA(std::optional<float> input);
|
||||
BraggIntegrationSettings& Integrator(IntegratorMode input);
|
||||
BraggIntegrationSettings& BackgroundTrimFraction(float input);
|
||||
BraggIntegrationSettings& MaxHKL(int input);
|
||||
BraggIntegrationSettings& MaxHKL(std::optional<int> input);
|
||||
|
||||
|
||||
[[nodiscard]] IntegratorMode GetIntegrator() const;
|
||||
@@ -54,5 +56,5 @@ public:
|
||||
|
||||
[[nodiscard]] float GetMinimumSigmaInRegardsToI() const;
|
||||
[[nodiscard]] float GetBackgroundTrimFraction() const;
|
||||
[[nodiscard]] int GetMaxHKL() const;
|
||||
[[nodiscard]] std::optional<int> GetMaxHKL() const;
|
||||
};
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ This is an UNSTABLE release. It includes many experimental features, as well as
|
||||
* rugnux: Per-image **geometry refinement** weights spots by confidence.
|
||||
* rugnux: `--min-image-cc` now works for rotation data (opt-in).
|
||||
* rugnux: New `--search-min-zeta` (rotation default 0.85).
|
||||
* rugnux: New `--max-hkl` sets how far the Bragg predictor walks the lattice (default 100, unchanged). An axis is truncated once `a/d_min` exceeds it, so a long-axis or high-resolution dataset was quietly losing its outermost reflections; raising it recovers them at a cost that grows as the cube.
|
||||
* Bragg prediction: how far the predictor walks the lattice is now a setting (`bragg_integration_settings.max_hkl`) rather than a hardcoded 100, for both rotation and stills. Offline (rugnux, viewer) it is **derived per crystal** from the refined cell as `ceil(max(a,b,c)/d_min) + 1` — an exact bound, so it recovers the outermost reflections that a fixed 100 was quietly truncating on a long-axis or high-resolution dataset, while costing less than before on a small cell. Measured: up to +17% observations and high-shell CC1/2 15.1% → 25.8%, with small cells bit-identical. `rugnux --max-hkl <n>` overrides it. Online the broker bootstraps 100, so a live acquisition keeps a predictable per-image cost whatever crystal is mounted; it is settable via the API and the frontend.
|
||||
* rugnux: Reports how close a symmetry axis lies to the spindle.
|
||||
* rugnux: Azimuthal-integration and spot-finding resolution limits default to the detector — including **rotation** data, which no longer keeps a 1.5 Å spot-finding limit (`--spot-high-resolution <num>` still sets one).
|
||||
* **API (breaking for older clients)**: the `image_scale_b` value is gone from the `plot_type` enum, so a client that requests that plot now gets an error rather than a curve. Also, `azim_int_settings.high_q_recipA` and `spot_finding_settings.high_resolution_limit` are no longer `required`. Both now mean "as far as the detector reaches" when unset, and are **omitted** from the response instead of carrying a placeholder value. A client generated from an rc.160-or-earlier spec that assumes the field is always present (the generated C++ `from_json` does `j.at(...)`) raises when it is missing. Because the azimuthal-integration limit now defaults to unset, a stock rc.161 broker omits `high_q_recipA` from `GET /config/azim_int` with no operator action. Regenerate the client (`jfjoch-client` 1.0.0-rc.161, `frontend/src/client`) or read both fields as optional.
|
||||
|
||||
+1
-1
@@ -268,7 +268,7 @@ Integration:
|
||||
| `--integrator <txt>` | Spot integrator: `gaussian` (profile-fit, default) \| `empirical` \| `boxsum` (classical fallback) |
|
||||
| `--integration-radius <r>` | Signal-box radius `r1`, or `r1,r2,r3` (px). One value ⇒ `r2=r1+2`, `r3=r1+4` |
|
||||
| `--background-trim <f>` | Monochromatic (rotation + still): symmetric trimmed-mean fraction for the background ring, 0≤f<0.5 (default 0.10; 0 = plain mean) — removes the high-side bias that over-subtracts weak high-angle spots |
|
||||
| `--max-hkl <n>` | Predict reflections with \|h\|,\|k\|,\|l\| ≤ `n` (default 100, max 511). An axis is truncated once `a/d_min` exceeds `n`, so a long axis — or a short one taken to high resolution — silently loses its outermost reflections. Cost grows as `(2n+1)³` candidates per frame, so raise it only where the cell needs the room |
|
||||
| `--max-hkl <n>` | Predict reflections with \|h\|,\|k\|,\|l\| ≤ `n` (max 511). By default this is derived per crystal from the refined cell as `ceil(max(a,b,c)/d_min) + 1`, which is the exact bound: the predictor keeps only \|q\| ≤ 1/d_min and `h = a·q`, so no reflection can lie outside it and no candidate inside it is wasted on a shorter axis. Set it only to override that |
|
||||
| `--bandwidth <num>` | Relative X-ray bandwidth FWHM (e.g. `0.01` for a 1% DMM); default from file or 0 (monochromatic) |
|
||||
|
||||
Geometry overrides (defaults are taken from the input file; override them to reprocess with a corrected geometry):
|
||||
|
||||
@@ -7,6 +7,7 @@ Settings for Bragg spot integration
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**integration_model** | [**IntegrationModel**](IntegrationModel.md) | | [default to IntegrationModel.PROFILEGAUSSIAN]
|
||||
**max_hkl** | **int** | How far the Bragg predictor walks the lattice: reflections with |h|,|k|,|l| above this are never predicted. An axis is truncated once a/d_min exceeds it, so a long axis - or a short one taken to high resolution - loses its outermost reflections. The cost grows as the cube (2n+1)^3 of candidates per image, which is why online keeps a fixed, predictable value instead of taking it from whichever crystal is mounted. Omitting the field selects the default above; it is never interpreted as \"choose per crystal\". The offline tools (rugnux, viewer) do derive it from the refined cell when it is left unset there, but that is their own default and is not reachable through this API. | [optional] [default to 100]
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -1584,6 +1584,19 @@ export type pcie_devices = Array<{
|
||||
*/
|
||||
export type bragg_integration_settings = {
|
||||
integration_model: integration_model;
|
||||
/**
|
||||
* How far the Bragg predictor walks the lattice: reflections with |h|,|k|,|l| above this are
|
||||
* never predicted. An axis is truncated once a/d_min exceeds it, so a long axis - or a short
|
||||
* one taken to high resolution - loses its outermost reflections. The cost grows as the cube
|
||||
* (2n+1)^3 of candidates per image, which is why online keeps a fixed, predictable value
|
||||
* instead of taking it from whichever crystal is mounted.
|
||||
*
|
||||
* Omitting the field selects the default above; it is never interpreted as "choose per
|
||||
* crystal". The offline tools (rugnux, viewer) do derive it from the refined cell when it is
|
||||
* left unset there, but that is their own default and is not reachable through this API.
|
||||
*
|
||||
*/
|
||||
max_hkl?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -700,7 +700,8 @@ export const zPcieDevices = z.array(z.object({
|
||||
* Settings for Bragg spot integration
|
||||
*/
|
||||
export const zBraggIntegrationSettings = z.object({
|
||||
integration_model: zIntegrationModel
|
||||
integration_model: zIntegrationModel,
|
||||
max_hkl: z.int().gte(1).lte(511).optional().default(100)
|
||||
});
|
||||
|
||||
export const zZeromqMetadataSettings = z.object({
|
||||
|
||||
@@ -7,6 +7,7 @@ import MenuItem from "@mui/material/MenuItem";
|
||||
import {bragg_integration_settings, integration_model} from "../client";
|
||||
import {putConfigBraggIntegrationMutation} from "../client/@tanstack/react-query.gen";
|
||||
import _ from "lodash";
|
||||
import NumberTextField from "./NumberTextField";
|
||||
import SettingsPanel from "./SettingsPanel";
|
||||
import {useUpload} from "./useUpload";
|
||||
|
||||
@@ -14,19 +15,25 @@ type MyProps = {
|
||||
s?: bragg_integration_settings
|
||||
}
|
||||
|
||||
const DEFAULT_MAX_HKL = 100;
|
||||
|
||||
const default_bragg_integration_settings: bragg_integration_settings = {
|
||||
integration_model: integration_model.PROFILE_GAUSSIAN
|
||||
integration_model: integration_model.PROFILE_GAUSSIAN,
|
||||
max_hkl: DEFAULT_MAX_HKL
|
||||
};
|
||||
|
||||
function BraggIntegrationSettings({s: serverS}: MyProps) {
|
||||
const [s, setS] = useState<bragg_integration_settings>(default_bragg_integration_settings);
|
||||
const [lastDownloadedS, setLastDownloadedS] = useState<bragg_integration_settings>(default_bragg_integration_settings);
|
||||
const [downloadCounter, setDownloadCounter] = useState(0);
|
||||
const [maxHklError, setMaxHklError] = useState(false);
|
||||
const {submit, pending, snackbar} = useUpload(putConfigBraggIntegrationMutation());
|
||||
|
||||
useEffect(() => {
|
||||
if ((serverS !== undefined) && !_.isEqual(serverS, lastDownloadedS)) {
|
||||
setS(serverS);
|
||||
setLastDownloadedS(serverS);
|
||||
setDownloadCounter(c => c + 1);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [serverS]);
|
||||
@@ -39,7 +46,7 @@ function BraggIntegrationSettings({s: serverS}: MyProps) {
|
||||
|
||||
return (
|
||||
<SettingsPanel title="Integration" dirty={dirty}
|
||||
onUpload={() => submit(s)} uploadDisabled={pending} snackbar={snackbar}>
|
||||
onUpload={() => submit(s)} uploadDisabled={pending || maxHklError} snackbar={snackbar}>
|
||||
<b>Integration model</b>
|
||||
<FormControl sx={{width: "80%"}}>
|
||||
<InputLabel>Integration model</InputLabel>
|
||||
@@ -53,6 +60,25 @@ function BraggIntegrationSettings({s: serverS}: MyProps) {
|
||||
<MenuItem value={integration_model.BOX_SUM}>Box summation (simpler, faster)</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<b>Prediction range</b>
|
||||
<FormControl sx={{width: "80%"}}>
|
||||
<NumberTextField
|
||||
default={DEFAULT_MAX_HKL}
|
||||
start_val={s.max_hkl}
|
||||
label={"Maximum |h|, |k|, |l|"}
|
||||
min={1}
|
||||
max={511}
|
||||
counter={downloadCounter}
|
||||
callback={(val: number, err: boolean) => {
|
||||
setS(prev => ({...prev, max_hkl: val}));
|
||||
setMaxHklError(err);
|
||||
}}
|
||||
fullWidth/>
|
||||
</FormControl>
|
||||
Reflections beyond this index are never predicted, so an axis is truncated once its length
|
||||
divided by the resolution limit exceeds it. The cost grows as the cube, per image - raise it
|
||||
only for a long cell.
|
||||
</SettingsPanel>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <cstdlib>
|
||||
#include "IndexAndRefine.h"
|
||||
@@ -19,6 +21,31 @@ namespace {
|
||||
// spends. Ceres' own default is 50; the per-image problem is small and converges well inside that,
|
||||
// so this bounds the pathological case rather than the normal one.
|
||||
constexpr int OFFLINE_REFINE_ITERATIONS = 50;
|
||||
|
||||
// How far the predictor has to walk the lattice for THIS crystal. The predictor keeps only
|
||||
// reflections with |q| <= 1/d_min, and h = a.q for the real-space axis a, so |h| <= a/d_min exactly
|
||||
// (Cauchy-Schwarz, equality when a lies along q); the same for k and l. Walking beyond
|
||||
// max(a,b,c)/d_min therefore generates only candidates the resolution cut throws away, and stopping
|
||||
// short of it silently truncates the outermost reflections of the longest axis.
|
||||
//
|
||||
// A fixed bound cannot be right for both: 100 covers a 150 A axis at 1.5 A and truncates the same
|
||||
// axis at 1.0 A, while being ~4x more cube than a small cell at 2 A ever needs. One index of margin
|
||||
// covers the rounding.
|
||||
int MaxHKLForCell(const CrystalLattice &latt, float d_min_A) {
|
||||
const UnitCell cell = latt.GetUnitCell();
|
||||
const float longest_axis_A = std::max({cell.a, cell.b, cell.c});
|
||||
return static_cast<int>(std::ceil(longest_axis_A / d_min_A)) + 1;
|
||||
}
|
||||
|
||||
// An explicit setting is enforced as given; otherwise the cell decides. The online path carries a
|
||||
// value (the broker bootstraps one and the API can change it), so live acquisition never has its
|
||||
// per-frame cost decided by the crystal that happened to be mounted.
|
||||
int PredictionMaxHKL(const DiffractionExperiment &experiment, const CrystalLattice &latt) {
|
||||
const auto &bragg = experiment.GetBraggIntegrationSettings();
|
||||
if (const auto fixed = bragg.GetMaxHKL())
|
||||
return *fixed;
|
||||
return MaxHKLForCell(latt, bragg.GetDMinLimit_A());
|
||||
}
|
||||
}
|
||||
|
||||
IndexAndRefine::IndexAndRefine(const DiffractionExperiment &x, IndexerThreadPool *indexer,
|
||||
@@ -438,7 +465,7 @@ void IndexAndRefine::QuickPredictAndIntegrate(DataMessage &msg,
|
||||
const BraggPredictionSettings settings_prediction{
|
||||
.high_res_A = experiment.GetBraggIntegrationSettings().GetDMinLimit_A(),
|
||||
.ewald_dist_cutoff = ewald_dist_cutoff,
|
||||
.max_hkl = experiment.GetBraggIntegrationSettings().GetMaxHKL(),
|
||||
.max_hkl = PredictionMaxHKL(experiment, latt),
|
||||
// Centering is a hypothesis to confirm, not assume: with no user-fixed space group, predict
|
||||
// in P so the centering-absent reflections are integrated and the space-group search can
|
||||
// confirm or disprove centering (and catch a missed superstructure). A user-fixed space
|
||||
|
||||
@@ -124,8 +124,8 @@ std::string RugnuxCommandLine(const ProcessConfig &config,
|
||||
// custom fraction, or 0 when the box is unchecked) to reproduce the GUI's choice faithfully.
|
||||
if (bragg.GetBackgroundTrimFraction() != 0.10f)
|
||||
add("--background-trim", num(bragg.GetBackgroundTrimFraction()));
|
||||
if (bragg.GetMaxHKL() != 100)
|
||||
add("--max-hkl", std::to_string(bragg.GetMaxHKL()));
|
||||
if (const auto max_hkl = bragg.GetMaxHKL())
|
||||
add("--max-hkl", std::to_string(*max_hkl));
|
||||
|
||||
if (config.rotation_indexing) {
|
||||
if (config.two_pass_rotation)
|
||||
|
||||
@@ -117,7 +117,7 @@ void print_usage() {
|
||||
std::cout << " Integration" << std::endl;
|
||||
std::cout << " --bandwidth <num> Relative X-ray bandwidth FWHM (e.g. 0.01 for 1% DMM); default from file or 0" << std::endl;
|
||||
std::cout << " --integration-radius <r> Signal-box radius r1, or r1,r2,r3 (px). One value => r2=r1+2, r3=r1+4" << std::endl;
|
||||
std::cout << " --max-hkl <n> Predict reflections with |h|,|k|,|l| <= n (default 100). Raise for a long axis: an axis is truncated once a/d_min exceeds n. Cost grows as (2n+1)^3" << std::endl;
|
||||
std::cout << " --max-hkl <n> Predict reflections with |h|,|k|,|l| <= n. Default: derived per crystal from the refined cell (ceil(longest axis / d_min) + 1), which is the exact bound - set it only to override that" << std::endl;
|
||||
std::cout << " --background-trim <f> Monochromatic (rotation + still): symmetric trimmed-mean fraction for the background ring (0<=f<0.5, default 0.10; 0 = plain mean). Removes the high-side bias that over-subtracts weak high-angle spots (broadband data keep the sigma-clip instead)" << std::endl;
|
||||
std::cout << " --integrator <txt> Spot integrator boxsum|gaussian|empirical (default: gaussian profile-fit; boxsum is the classical fallback)" << std::endl;
|
||||
std::cout << " --simple-stills stills: treat every reflection as a full (p=1, single-pass scale/merge); disables the default physical partiality post-refinement" << std::endl;
|
||||
@@ -1501,7 +1501,7 @@ static int RunRugnux(int argc, char **argv) {
|
||||
BraggIntegrationSettings bis = experiment.GetBraggIntegrationSettings();
|
||||
bis.MaxHKL(static_cast<int>(*max_hkl_arg));
|
||||
experiment.ImportBraggIntegrationSettings(bis);
|
||||
logger.Info("Predicting reflections with |h|,|k|,|l| <= {}", *max_hkl_arg);
|
||||
logger.Info("Predicting reflections with |h|,|k|,|l| <= {} (overriding the per-crystal bound)", *max_hkl_arg);
|
||||
}
|
||||
|
||||
if (background_trim_arg) {
|
||||
|
||||
Reference in New Issue
Block a user