From bf5f089a2de30883c84137b41801ee1350814023 Mon Sep 17 00:00:00 2001 From: lucas-wilkins Date: Mon, 4 Sep 2023 14:27:50 +0100 Subject: [PATCH] Working prototype --- .../ParticleEditor/DesignWindow.py | 21 +++++++++++++------ .../ParticleEditor/Plots/QCanvas.py | 14 ++++++++----- .../ParticleEditor/UI/DesignWindowUI.ui | 8 +++---- .../ParticleEditor/calculations/calculate.py | 7 +++++-- .../ParticleEditor/calculations/fq.py | 12 +++++------ .../ParticleEditor/datamodel/calculation.py | 6 +++++- .../ParticleEditor/sampling/points.py | 2 +- 7 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/sas/qtgui/Perspectives/ParticleEditor/DesignWindow.py b/src/sas/qtgui/Perspectives/ParticleEditor/DesignWindow.py index 931029e2a8..8f511d0d71 100644 --- a/src/sas/qtgui/Perspectives/ParticleEditor/DesignWindow.py +++ b/src/sas/qtgui/Perspectives/ParticleEditor/DesignWindow.py @@ -179,6 +179,9 @@ def onRadiusChanged(self): def onTimeEstimateParametersChanged(self): """ Called when the number of samples changes """ + # TODO: This needs to be updated based on the number of angular samples now + # Should have a n_points term + # Should have a n_points*n_angles # Update the estimate of time # Assume the amount of time is just determined by the number of @@ -250,6 +253,13 @@ def angularDistribution(self) -> AngularDistribution: """ Get the AngularDistribution object that represents the GUI selected orientational distribution""" return self.angularSamplingMethodSelector.generate_sampler() + def qSampling(self) -> QSample: + q_min = float(self.qMinBox.text()) # TODO: Use better box + q_max = float(self.qMaxBox.text()) + n_q = int(self.qSamplesBox.value()) + is_log = bool(self.useLogQ.isChecked()) + + return QSample(q_min, q_max, n_q, is_log) def spatialSampling(self) -> SpatialDistribution: """ Calculate the spatial sampling object based on current gui settings""" @@ -261,11 +271,11 @@ def spatialSampling(self) -> SpatialDistribution: seed = int(self.randomSeed.text()) if self.fixRandomSeed.isChecked() else None if sample_type == 0: - return GridSampling(radius=radius, n_points=n_points, seed=seed) + return GridSampling(radius=radius, desired_points=n_points) # return MixedSphereSample(radius=radius, n_points=n_points, seed=seed) elif sample_type == 1: - return UniformCubeSampling(radius=radius, n_points=n_points, seed=seed) + return UniformCubeSampling(radius=radius, desired_points=n_points, seed=seed) # return MixedCubeSample(radius=radius, n_points=n_points, seed=seed) else: @@ -307,6 +317,7 @@ def scatteringCalculation(self) -> ScatteringCalculation: is to be passed to the solver""" angular_distribution = self.angularDistribution() spatial_sampling = self.spatialSampling() + q_sampling = self.qSampling() particle_definition = self.particleDefinition() parameter_definition = self.parametersForCalculation() polarisation_vector = self.polarisationVector() @@ -314,6 +325,7 @@ def scatteringCalculation(self) -> ScatteringCalculation: bounding_surface_check = self.continuityCheck.isChecked() return ScatteringCalculation( + q_sampling=q_sampling, angular_sampling=angular_distribution, spatial_sampling_method=spatial_sampling, particle_definition=particle_definition, @@ -360,12 +372,9 @@ def display_calculation_result(self, scattering_result: ScatteringOutput): """ Update graphs and select tab""" # Plot - self.samplingCanvas.data = scattering_result - self.rdfCanvas.data = scattering_result - self.correlationCanvas.data = scattering_result self.outputCanvas.data = scattering_result - self.tabWidget.setCurrentIndex(7) # Move to output tab if complete + self.tabWidget.setCurrentIndex(5) # Move to output tab if complete def onFit(self): """ Fit functionality requested""" pass diff --git a/src/sas/qtgui/Perspectives/ParticleEditor/Plots/QCanvas.py b/src/sas/qtgui/Perspectives/ParticleEditor/Plots/QCanvas.py index 4d55803f04..95a3b67bc2 100644 --- a/src/sas/qtgui/Perspectives/ParticleEditor/Plots/QCanvas.py +++ b/src/sas/qtgui/Perspectives/ParticleEditor/Plots/QCanvas.py @@ -1,12 +1,13 @@ from __future__ import annotations +import numpy as np from typing import Optional from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure +from sas.qtgui.Perspectives.ParticleEditor.datamodel.calculation import ScatteringOutput -import numpy as np def spherical_form_factor(q, r): rq = r * q f = (np.sin(rq) - rq * np.cos(rq)) / (rq ** 3) @@ -31,12 +32,14 @@ def data(self): @data.setter def data(self, scattering_output: ScatteringOutput): + # print("Setting QPlot Data") + self._data = scattering_output self.axes.cla() - if self._data.q_space is not None: - plot_data = scattering_output.q_space.q_space_data + # print(self._data.q_space) + plot_data = self._data.q_space q_sample = plot_data.abscissa q_values = q_sample() @@ -52,8 +55,9 @@ def data(self, scattering_output: ScatteringOutput): # self.axes.axvline(0.30) # For comparisons: TODO: REMOVE - thing = spherical_form_factor(q_values, 50) - self.axes.loglog(q_values, thing*np.max(i_values)/np.max(thing)) + # thing = spherical_form_factor(q_values, 50) + # self.axes.loglog(q_values, thing*np.max(i_values)/np.max(thing)) + # It works, NICE! else: self.axes.semilogy(q_values, i_values) diff --git a/src/sas/qtgui/Perspectives/ParticleEditor/UI/DesignWindowUI.ui b/src/sas/qtgui/Perspectives/ParticleEditor/UI/DesignWindowUI.ui index 4ecfe96564..bc0dd39ce1 100644 --- a/src/sas/qtgui/Perspectives/ParticleEditor/UI/DesignWindowUI.ui +++ b/src/sas/qtgui/Perspectives/ParticleEditor/UI/DesignWindowUI.ui @@ -20,7 +20,7 @@ - 2 + 0 @@ -209,7 +209,7 @@ - Ang + Ang<sup>-1</sup> @@ -232,7 +232,7 @@ - Logaritmic (applies only to 1D) + Logaritmic true @@ -436,7 +436,7 @@ - Ang + Ang<sup>-1</sup> diff --git a/src/sas/qtgui/Perspectives/ParticleEditor/calculations/calculate.py b/src/sas/qtgui/Perspectives/ParticleEditor/calculations/calculate.py index 2ca1893c8a..15259c6f0d 100644 --- a/src/sas/qtgui/Perspectives/ParticleEditor/calculations/calculate.py +++ b/src/sas/qtgui/Perspectives/ParticleEditor/calculations/calculate.py @@ -1,6 +1,7 @@ import time -from sas.qtgui.Perspectives.ParticleEditor.datamodel.calculation import ScatteringCalculation, ScatteringOutput +from sas.qtgui.Perspectives.ParticleEditor.datamodel.calculation import \ + ScatteringCalculation, QSpaceScattering, ScatteringOutput from sas.qtgui.Perspectives.ParticleEditor.calculations.fq import scattering_via_fq from sas.qtgui.Perspectives.ParticleEditor.calculations.boundary_check import ( check_sld_continuity_at_boundary, check_mag_zero_at_boundary) @@ -40,8 +41,10 @@ def calculate_scattering(calculation: ScatteringCalculation) -> ScatteringOutput q_sample=q_dist, angular_distribution=angular_dist) + q_data = QSpaceScattering(q_dist, scattering) + output = ScatteringOutput( - q_space=scattering, + q_space=q_data, calculation_time=time.time() - start_time, seed_used=None) diff --git a/src/sas/qtgui/Perspectives/ParticleEditor/calculations/fq.py b/src/sas/qtgui/Perspectives/ParticleEditor/calculations/fq.py index 957644112e..f94404e42a 100644 --- a/src/sas/qtgui/Perspectives/ParticleEditor/calculations/fq.py +++ b/src/sas/qtgui/Perspectives/ParticleEditor/calculations/fq.py @@ -7,8 +7,6 @@ from scipy.spatial.distance import cdist from sas.qtgui.Perspectives.ParticleEditor.datamodel.calculation import ( - ScatteringCalculation, ScatteringOutput, SamplingDistribution, - QSpaceScattering, QSpaceCalcDatum, RealSpaceScattering, SLDDefinition, MagnetismDefinition, AngularDistribution, QSample, CalculationParameters) from sas.qtgui.Perspectives.ParticleEditor.sampling.chunking import SingleChunk, pairwise_chunk_iterator @@ -28,19 +26,19 @@ def scattering_via_fq( q_magnitudes = q_sample() direction_vectors, direction_weights = angular_distribution.sample_points_and_weights() - fq = np.zeros((angular_distribution.n_points, q_sample.n_points)) # Dictionary for fq for all angles + fq = np.zeros((angular_distribution.n_points, q_sample.n_points), dtype=complex) # Dictionary for fq for all angles for x, y, z in PointGeneratorStepper(point_generator, chunk_size): - sld = run_sld(sld_definition, parameters, x, y, z) + sld = run_sld(sld_definition, parameters, x, y, z).reshape(-1, 1) # TODO: Magnetism for direction_index, direction_vector in enumerate(direction_vectors): - r = np.sqrt(x*direction_vector[0] + y*direction_vector[1] + z*direction_vectors[2]) + projected_distance = x*direction_vector[0] + y*direction_vector[1] + z*direction_vector[2] - i_r_dot_q = np.multiply.outer(r, 1j*q_magnitudes) + i_r_dot_q = np.multiply.outer(projected_distance, 1j*q_magnitudes) if direction_index in fq: fq[direction_index, :] = np.sum(sld*np.exp(i_r_dot_q), axis=0) @@ -48,7 +46,7 @@ def scattering_via_fq( fq[direction_index, :] += np.sum(sld*np.exp(i_r_dot_q), axis=0) f_squared = fq.real**2 + fq.imag**2 - f_squared *= direction_weights + f_squared *= direction_weights.reshape(-1,1) return np.sum(f_squared, axis=0) diff --git a/src/sas/qtgui/Perspectives/ParticleEditor/datamodel/calculation.py b/src/sas/qtgui/Perspectives/ParticleEditor/datamodel/calculation.py index c869202c74..c55ce7f1e1 100644 --- a/src/sas/qtgui/Perspectives/ParticleEditor/datamodel/calculation.py +++ b/src/sas/qtgui/Perspectives/ParticleEditor/datamodel/calculation.py @@ -49,9 +49,13 @@ def generate(self, start_index: int, end_index: int) -> VectorComponents3: """ Generate points from start_index up to end_index """ @abstractmethod - def bounding_surface_check_points(self) -> VectorComponents3: + def _bounding_surface_check_points(self) -> np.ndarray: """ Points used to check that the SLD/magnetism vector are zero outside the sample space""" + def bounding_surface_check_points(self) -> VectorComponents3: + pts = self._bounding_surface_check_points() + return pts[:, 0], pts[:, 1], pts[:, 2] + class AngularDistribution(ABC): """ Base class for angular distributions """ diff --git a/src/sas/qtgui/Perspectives/ParticleEditor/sampling/points.py b/src/sas/qtgui/Perspectives/ParticleEditor/sampling/points.py index f043f002e2..b07f41eb35 100644 --- a/src/sas/qtgui/Perspectives/ParticleEditor/sampling/points.py +++ b/src/sas/qtgui/Perspectives/ParticleEditor/sampling/points.py @@ -31,7 +31,7 @@ class BoundedByCube(SpatialDistribution): [-1,-1, 1], [-1,-1,-1], ], dtype=float) - def bounding_surface_check_points(self) -> VectorComponents3: + def _bounding_surface_check_points(self) -> VectorComponents3: return BoundedByCube._boundary_base_points * self.radius