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

create HYSPEC intensity matrix #24

Merged
merged 23 commits into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4ac5970
inital tests setup
KyleQianliMa Jan 13, 2025
013db00
more tests
KyleQianliMa Jan 13, 2025
b326d0e
Merge branch 'next' into powder_model
KyleQianliMa Jan 14, 2025
77a55e4
change return type to dict
KyleQianliMa Jan 14, 2025
bad96d6
test calculate different graph options
KyleQianliMa Jan 14, 2025
ae0f40f
test deltaE
KyleQianliMa Jan 14, 2025
65ff197
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 14, 2025
bf986e1
logic and test to handle DeltaE < -Ei
KyleQianliMa Jan 15, 2025
a403058
fixing maths in model.
KyleQianliMa Jan 16, 2025
7666585
chagne plot results to universal key workd "intensity" and update tests
KyleQianliMa Jan 16, 2025
fe2ab34
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 16, 2025
66e4978
minor code clean ups
KyleQianliMa Jan 16, 2025
e83d20f
test -S2
KyleQianliMa Jan 16, 2025
52e16d9
Clean up some code
AndreiSavici Jan 17, 2025
0af3a19
Merge branch 'next' into powder_model
AndreiSavici Jan 17, 2025
95e8d91
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 17, 2025
8eb9ae7
Fix accidental renaming
AndreiSavici Jan 18, 2025
9981045
Clean up code and add comments
AndreiSavici Jan 18, 2025
15cbe02
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 18, 2025
90de29f
Clean up code and fix pre-commit
AndreiSavici Jan 18, 2025
800f163
Clean up test
AndreiSavici Jan 18, 2025
259eb4f
Add consistency check
AndreiSavici Jan 18, 2025
6e8f5b3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 18, 2025
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
6 changes: 3 additions & 3 deletions scripts/PPTPmodel-Kyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ def polarization_powder(self,Ei, EMin, S2, alpha_p, plot_options="alpha",left=Tr
Qx= (-1 if left else 1) * kf2d*np.sqrt((1-cos_theta**2))

cos_ang_PQ = (Qx*Px + Qz*Pz)/Q2d/np.sqrt(Px**2+Pz**2)
cos_ang_PQ[cos_ang_PQ**2 <0.4] = np.nan
cos_ang_PQ[cos_ang_PQ**2 >0.6] = np.nan
# cos_ang_PQ[cos_ang_PQ**2 <0.4] = np.nan
# cos_ang_PQ[cos_ang_PQ**2 >0.6] = np.nan
ang_PQ = np.degrees(np.arccos(cos_ang_PQ))

kf=np.sqrt(Ei-E)*SE2K
Expand Down Expand Up @@ -124,7 +124,7 @@ def polarization_powder(self,Ei, EMin, S2, alpha_p, plot_options="alpha",left=Tr

if __name__ == "__main__":
obj = Model()
output = obj.polarization_powder(20, None, 60, 30,plot_options="alpha",left=True)
output = obj.polarization_powder(20, None, 40, 0,plot_options="cos^2(a)",left=True)
Q_low, Q_hi, E, Q2d, E2d, ang_PQ = output[0], output[1], output[2], output[3], output[4], output[5]

fig, ax = plt.subplots()
Expand Down
5 changes: 5 additions & 0 deletions src/hyspecppt/hppt/experiment_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
DEFAULT_MODE = dict(current_experiment_type="single_crystal")
# maximum momentum transfer
MAX_MODQ = 15
# number of points in the plot
N_POINTS = 200
# tank half-width
TANK_HALF_WIDTH = 30.0


# invalid style
INVALID_QLINEEDIT = """
Expand Down
253 changes: 127 additions & 126 deletions src/hyspecppt/hppt/hppt_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
DEFAULT_LATTICE,
DEFAULT_MODE,
MAX_MODQ,
N_POINTS,
PLOT_TYPES,
TANK_HALF_WIDTH,
)

logger = logging.getLogger("hyspecppt")
Expand All @@ -20,19 +22,19 @@
class SingleCrystalParameters:
"""Model for single crystal calculations"""

a: float = DEFAULT_LATTICE["a"]
b: float = DEFAULT_LATTICE["b"]
c: float = DEFAULT_LATTICE["c"]
alpha: float = DEFAULT_LATTICE["alpha"]
beta: float = DEFAULT_LATTICE["beta"]
gamma: float = DEFAULT_LATTICE["gamma"]
h: float = DEFAULT_LATTICE["h"]
k: float = DEFAULT_LATTICE["k"]
l: float = DEFAULT_LATTICE["l"]
a: float
b: float
c: float
alpha: float
beta: float
gamma: float
h: float
k: float
l: float

def __init__(self) -> None:
"""Constructor"""
return
self.set_parameters(DEFAULT_LATTICE)

def set_parameters(self, params: dict[str, float]) -> None:
"""Store single crystal parameters
Expand All @@ -54,63 +56,58 @@ def set_parameters(self, params: dict[str, float]) -> None:

def get_parameters(self) -> dict[str, float]:
"""Returns all the parameters as a dictionary"""
try:
return dict(
a=self.a,
b=self.b,
c=self.c,
alpha=self.alpha,
beta=self.beta,
gamma=self.gamma,
h=self.h,
k=self.k,
l=self.l,
)
except AttributeError:
logger.error("The parameters were not initialized-get_parameters-SingleCrystalParameters")
return dict(
a=self.a,
b=self.b,
c=self.c,
alpha=self.alpha,
beta=self.beta,
gamma=self.gamma,
h=self.h,
k=self.k,
l=self.l,
)

def calculate_modQ(self) -> float:
"""Returns |Q| from lattice parameters and h, k, l"""
try:
ca = np.cos(np.radians(self.alpha))
sa = np.sin(np.radians(self.alpha))
cb = np.cos(np.radians(self.beta))
sb = np.sin(np.radians(self.beta))
cg = np.cos(np.radians(self.gamma))
sg = np.sin(np.radians(self.gamma))
vabg = np.sqrt(1 - ca**2 - cb**2 - cg**2 + 2 * ca * cb * cg)
astar = sa / (self.a * vabg)
bstar = sb / (self.b * vabg)
cstar = sg / (self.c * vabg)
cas = (cb * cg - ca) / (sb * sg) # noqa: F841
cbs = (cg * ca - cb) / (sg * sa)
cgs = (ca * cb - cg) / (sa * sb)

# B matrix
B = np.array(
[
[astar, bstar * cgs, cstar * cbs],
[0, bstar * np.sqrt(1 - cgs**2), -cstar * np.sqrt(1 - cbs**2) * ca],
[0, 0, 1.0 / self.c],
]
)

modQ = 2 * np.pi * np.linalg.norm(B.dot([self.h, self.k, self.l]))
return modQ
except AttributeError:
logger.error("The parameters were not initialized-calculate_modQ")
ca = np.cos(np.radians(self.alpha))
sa = np.sin(np.radians(self.alpha))
cb = np.cos(np.radians(self.beta))
sb = np.sin(np.radians(self.beta))
cg = np.cos(np.radians(self.gamma))
sg = np.sin(np.radians(self.gamma))
vabg = np.sqrt(1 - ca**2 - cb**2 - cg**2 + 2 * ca * cb * cg)
astar = sa / (self.a * vabg)
bstar = sb / (self.b * vabg)
cstar = sg / (self.c * vabg)
cas = (cb * cg - ca) / (sb * sg) # noqa: F841
cbs = (cg * ca - cb) / (sg * sa)
cgs = (ca * cb - cg) / (sa * sb)

# B matrix
B = np.array(
[
[astar, bstar * cgs, cstar * cbs],
[0, bstar * np.sqrt(1 - cgs**2), -cstar * np.sqrt(1 - cbs**2) * ca],
[0, 0, 1.0 / self.c],
]
)

modQ = 2 * np.pi * np.linalg.norm(B.dot([self.h, self.k, self.l]))
return modQ


class CrosshairParameters:
"""Model for the crosshair parameters"""

modQ: float = DEFAULT_CROSSHAIR["modQ"]
DeltaE: float = DEFAULT_CROSSHAIR["DeltaE"]
current_experiment_type: str = DEFAULT_MODE["current_experiment_type"]
modQ: float
DeltaE: float
current_experiment_type: str
sc_parameters: SingleCrystalParameters
experiment_types = ["single_crystal", "powder"]

def __init__(self):
def __init__(self) -> None:
"""Constructor"""
self.set_crosshair(**DEFAULT_MODE, **DEFAULT_CROSSHAIR)
self.sc_parameters = SingleCrystalParameters()

def set_crosshair(self, current_experiment_type: str, DeltaE: float = None, modQ: float = None) -> None:
Expand All @@ -132,18 +129,23 @@ def get_crosshair(self) -> dict[str, float]:
return dict(DeltaE=self.DeltaE, modQ=modQ)
return dict(DeltaE=self.DeltaE, modQ=self.modQ)

def get_experiment_type(self) -> str:
"""Return experiment type"""
return self.current_experiment_type


class HyspecPPTModel:
"""Main model"""

Ei: float = DEFAULT_EXPERIMENT["Ei"]
S2: float = DEFAULT_EXPERIMENT["S2"]
alpha_p: float = DEFAULT_EXPERIMENT["alpha_p"]
plot_type: str = DEFAULT_EXPERIMENT["plot_type"]
Ei: float
S2: float
alpha_p: float
plot_type: str
cp: CrosshairParameters

def __init__(self):
"""Constructor"""
self.set_experiment_data(**DEFAULT_EXPERIMENT)
self.cp = CrosshairParameters()

def set_single_crystal_data(self, params: dict[str, float]) -> None:
Expand All @@ -165,70 +167,69 @@ def set_experiment_data(self, Ei: float, S2: float, alpha_p: float, plot_type: s
self.plot_type = plot_type

def get_experiment_data(self) -> dict[str, float]:
data = dict()

data["Ei"] = self.Ei
data["S2"] = self.S2
data["alpha_p"] = self.alpha_p
data["plot_type"] = self.plot_type
data = dict(Ei=self.Ei, S2=self.S2, alpha_p=self.alpha_p, plot_type=self.plot_type)
return data

def get_graph_data(self) -> list[float, float, float, list, list, list]:
return self.calculate_graph_data()

def calculate_graph_data(self) -> list[float]:
"""Returns a list of [Q_low, Q_hi, E, Q2d, E2d, data of plot_types]"""
try:
SE2K = np.sqrt(2e-3 * e * m_n) * 1e-10 / hbar
# def Ei, Emin = - Ei to create Qmin, Qmax to generate plot range
if self.cp.DeltaE is not None and self.cp.DeltaE < -self.Ei:
EMin = -self.Ei
E = np.linspace(EMin, self.Ei * 0.9, 200)

kfmin = np.sqrt(self.Ei - EMin) * SE2K
ki = np.sqrt(self.Ei) * SE2K

# S2= 60
# Create Qmin and Qmax
Qmax = np.sqrt(ki**2 + kfmin**2 - 2 * ki * kfmin * np.cos(np.radians(self.S2 + 30))) # Q=ki or Q=
Qmin = 0
Q = np.linspace(Qmin, Qmax, 200)

# Create 2D array
E2d, Q2d = np.meshgrid(E, Q)

Ef2d = self.Ei - E2d
kf2d = np.sqrt(Ef2d) * SE2K

Px = np.cos(np.radians(self.alpha_p))
Pz = np.sin(np.radians(self.alpha_p))

cos_theta = (ki**2 + kf2d**2 - Q2d**2) / (2 * ki * kf2d)
cos_theta[cos_theta < np.cos(np.radians(self.S2 + 30))] = np.nan
cos_theta[cos_theta > np.cos(np.radians(self.S2 - 30))] = np.nan # cos(30)

Qz = ki - kf2d * cos_theta

if self.S2 >= 30:
Qx = (-1) * kf2d * np.sqrt((1 - cos_theta**2))
elif self.S2 <= -30:
Qx = kf2d * np.sqrt((1 - cos_theta**2))

cos_ang_PQ = (Qx * Px + Qz * Pz) / Q2d / np.sqrt(Px**2 + Pz**2)
ang_PQ = np.degrees(np.arccos(cos_ang_PQ))

kf = np.sqrt(self.Ei - E) * SE2K

Q_low = np.sqrt(ki**2 + kf**2 - 2 * ki * kf * np.cos(np.radians(self.S2 - 30)))
Q_hi = np.sqrt(ki**2 + kf**2 - 2 * ki * kf * np.cos(np.radians(self.S2 + 30)))

if self.plot_type == PLOT_TYPES[0]: # alpha
return [Q_low, Q_hi, E, Q2d, E2d, ang_PQ]

if self.plot_type == PLOT_TYPES[1]: # cos^2(alpha)
return [Q_low, Q_hi, E, Q2d, E2d, np.cos(np.radians(ang_PQ)) ** 2]

if self.plot_type == PLOT_TYPES[2]: # "(cos^2(a)+1)/2"
return [Q_low, Q_hi, E, Q2d, E2d, (np.cos(np.radians(ang_PQ)) ** 2 + 1) / 2]
except AttributeError:
logger.error("The parameters were not initialized")
def calculate_graph_data(self) -> dict[str, np.array]:
"""Returns a dictionary of arrays [Q_low, Q_hi, E, Q2d, E2d, data of plot_types]"""
# constant to transform from energy in meV to momentum in Angstrom^-1
SE2K = np.sqrt(2e-3 * e * m_n) * 1e-10 / hbar

# adjust minimum energy
if self.cp.DeltaE is not None and self.cp.DeltaE <= -self.Ei:
EMin = 1.2 * self.cp.DeltaE
else:
EMin = -self.Ei

E = np.linspace(EMin, self.Ei * 0.9, N_POINTS)

# Calculate lines for the edges of the tank
ki = np.sqrt(self.Ei) * SE2K
kf = np.sqrt(self.Ei - E) * SE2K
Q_low = np.sqrt(ki**2 + kf**2 - 2 * ki * kf * np.cos(np.radians(np.abs(self.S2) - TANK_HALF_WIDTH)))
Q_hi = np.sqrt(ki**2 + kf**2 - 2 * ki * kf * np.cos(np.radians(np.abs(self.S2) + TANK_HALF_WIDTH)))

# Create 2D array
Q = np.linspace(0, np.max(Q_hi), N_POINTS)
E2d, Q2d = np.meshgrid(E, Q)
Ef2d = self.Ei - E2d
kf2d = np.sqrt(Ef2d) * SE2K

# Calculate the angle between Q and z axis. Set to NAN values outside the detector range
cos_theta = (ki**2 + kf2d**2 - Q2d**2) / (2 * ki * kf2d)
cos_theta[cos_theta < np.cos(np.radians(np.abs(self.S2) + TANK_HALF_WIDTH))] = np.nan
cos_theta[cos_theta > np.cos(np.radians(np.abs(self.S2) - TANK_HALF_WIDTH))] = np.nan

# Calculate Qz
Qz = ki - kf2d * cos_theta

# Calculate Qx. Note that is in the opposite direction as detector position
if self.S2 >= TANK_HALF_WIDTH:
Qx = (-1) * kf2d * np.sqrt((1 - cos_theta**2))
elif self.S2 <= -TANK_HALF_WIDTH:
Qx = kf2d * np.sqrt((1 - cos_theta**2))

# Transform polarization angle in the lab frame to vector
Px = np.cos(np.radians(self.alpha_p))
Pz = np.sin(np.radians(self.alpha_p))

# Calculate angle between polarization vector and momentum transfer
cos_ang_PQ = (Qx * Px + Qz * Pz) / Q2d / np.sqrt(Px**2 + Pz**2)

# Select return value for intensity
if self.plot_type == PLOT_TYPES[0]: # alpha
ang_PQ = np.arccos(cos_ang_PQ)
intensity = np.degrees(ang_PQ)
elif self.plot_type == PLOT_TYPES[1]: # cos^2(alpha)
intensity = cos_ang_PQ**2
elif self.plot_type == PLOT_TYPES[2]: # "(cos^2(a)+1)/2"
intensity = (cos_ang_PQ**2 + 1) / 2

return dict(
Q_low=Q_low,
Q_hi=Q_hi,
E=E,
Q2d=Q2d,
E2d=E2d,
intensity=intensity,
)
15 changes: 4 additions & 11 deletions src/hyspecppt/hppt/hppt_presenter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Presenter for the Main tab"""

from .experiment_settings import DEFAULT_CROSSHAIR, DEFAULT_EXPERIMENT, DEFAULT_LATTICE, DEFAULT_MODE, PLOT_TYPES
from .experiment_settings import PLOT_TYPES


class HyspecPPTPresenter:
Expand All @@ -20,20 +20,13 @@ def __init__(self, view: any, model: any):
self.view.connect_sc_mode_switch(self.handle_switch_to_sc)

# populate fields
self.view.sc_widget.set_values(DEFAULT_LATTICE)
self.view.sc_widget.set_values(self.model.get_single_crystal_data())
self.view.experiment_widget.initializeCombo(PLOT_TYPES)
self.view.experiment_widget.set_values(DEFAULT_EXPERIMENT)
self.view.crosshair_widget.set_values(DEFAULT_CROSSHAIR)

# model init
# to be removed needs to happen in the model
self.model.set_experiment_data(**DEFAULT_EXPERIMENT)
self.model.set_crosshair_data(**DEFAULT_CROSSHAIR, **DEFAULT_MODE)
self.model.set_single_crystal_data(params=DEFAULT_LATTICE)
self.view.experiment_widget.set_values(self.model.get_experiment_data())

# set default selection mode
experiment_type = self.view.selection_widget.powder_label
if DEFAULT_MODE["current_experiment_type"].startswith("single"):
if self.model.cp.get_experiment_type().startswith("single"):
experiment_type = self.view.selection_widget.sc_label
self.view.selection_widget.selector_init(experiment_type) # pass the default mode from experiment type

Expand Down
Loading
Loading