From f2d4df249c1ea3b5bfa52aaaa87d36058af74688 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Tue, 6 Jan 2026 22:36:15 +0000 Subject: [PATCH 01/17] [#1166] Type hinting, docstr and better default for d3cross, d2cross --- .../electrons/emission/_xray_thin_target.py | 65 ++++++++++++------ .../emission/_xray_thin_target_integrated.py | 27 +++++--- .../_xray_thin_target_integrated_plot.py | 66 ++++++++++++++----- 3 files changed, 111 insertions(+), 47 deletions(-) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target.py b/tofu/physics_tools/electrons/emission/_xray_thin_target.py index 155497344..0b286a8a5 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target.py @@ -2,6 +2,7 @@ import os import warnings +from typing import Any, Optional # Dict import numpy as np @@ -12,6 +13,9 @@ import datastock as ds +TupleDict = tuple[dict] + + # #################################################### # #################################################### # DEFAULT @@ -28,8 +32,9 @@ def get_xray_thin_d3cross_ei( - # inputs - Z=None, + # target ion charge + Z: Optional[int] = None, + # Energy E_e0_eV=None, E_e1_eV=None, # directions @@ -37,19 +42,19 @@ def get_xray_thin_d3cross_ei( theta_e=None, dphi=None, # hypergeometric parameter - ninf=None, - source=None, + ninf: Optional[int] = None, + source: Optional[str] = None, # output customization - per_energy_unit=None, + per_energy_unit: Optional[str] = None, # version - version=None, + version: Optional[str] = None, # debug - debug=None, -): + debug: Optional[bool] = None, +) -> dict: """ Return a differential cross-section for thin-target bremsstrahlung - Allowws several formulas (version): - - 'BE': Elwert-Haug [1] + Allows several formulas (version): + - 'EH': Elwert-Haug [1] (default) . most general and accurate . Uses Sommerfield-Maue eigenfunctions . eq. (30) in [1] @@ -74,14 +79,15 @@ def get_xray_thin_d3cross_ei( Physics Reports, vol. 243, p. 317—353, 1994. - Inputs: + Inputs: (all angles in rad) E_e0_eV = kinetic energy of incident electron in eV E_e1_eV = kinetic energy of scattered electron in eV theta_e = (spherical) theta angle of scattered e vs incident e theta_ph = (spherical) theta angle of photon vs incident e phi_e = (spherical) phi angle of scattered e vs incident e phi_ph = (spherical) theta angle of photon vs incident e - (all angles in rad) + version = 'EH', 'BH' or 'BHE' + Limitations: - 'EH' implementation currently stalled because: @@ -1326,8 +1332,8 @@ def _hyp2F1( bb=None, cc=None, zz=None, - ninf=None, - source=None, + ninf: Optional[int] = None, + source: Optional[str] = None, ): """ Hypergeometric function 2F1 with complex arguments @@ -1389,8 +1395,12 @@ def _hyp2F1( # Number of terms # ---------- - if ninf is None: - ninf = 50 + ninf = ds._generic_check._check_var( + ninf, 'ninf', + types=int, + default=50, + sign='>0', + ) nn = np.arange(0, ninf)[None, :] @@ -1495,11 +1505,11 @@ def _hyp2F1( def plot_xray_thin_d3cross_ei_vs_Literature( - version=None, - ninf=None, - source=None, - dax=None, -): + version: Optional[str] = None, + ninf: Optional[int] = None, + source: Optional[str] = None, + dax: Optional[dict[str, Any]] = None, +) -> TupleDict: """ Compare computed cross-sections vs literature values from Elwert-Haug Triply differential cross-section @@ -1509,6 +1519,19 @@ def plot_xray_thin_d3cross_ei_vs_Literature( [2] W. Nakel, Physics Reports, 243, p. 317—353, 1994 + Return: + ------- + dax: dict + Dict of axes + ddata_iso: dict + Dict of + ddata_ph_dist: dict + Dict of + ddata_ph_dist_nakel: dict + Dict of + ddata_ph_spect_nakel: dict + Dict of + """ # -------------- diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py index 2cb60228c..0c9e68c5e 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py @@ -1,6 +1,7 @@ import os +from typing import Any, Optional # Dict import numpy as np @@ -14,6 +15,9 @@ from . import _xray_thin_target +TupleDict = tuple[dict] + + # #################################################### # #################################################### # DEFAULT @@ -40,25 +44,28 @@ def get_xray_thin_d2cross_ei_integrated_thetae_dphi( - # inputs - Z=None, + # target ion charge + Z: Optional[int] = None, + # energies E_e0_eV=None, E_ph_eV=None, theta_ph=None, # hypergeometric parameter - ninf=None, - source=None, + ninf: Optional[int] = None, + source: Optional[str] = None, # integration parameters nthetae=None, ndphi=None, # output customization - per_energy_unit=None, + per_energy_unit: Optional[str] = None, # version - version=None, + version: Optional[str] = None, # verb - verb=None, - verb_tab=None, -): + verb: Optional[bool] = None, + verb_tab: Optional[str] = None, +) -> dict: + """ Compute d2cross, which is d3cross integrated over dphi + """ # ------------ # inputs @@ -306,7 +313,7 @@ def _check( # #################################################### -def plot_xray_thin_d2cross_ei_vs_literature(): +def plot_xray_thin_d2cross_ei_vs_literature() -> TupleDict: """ Plot electron-angle-integrated cross section vs [1] G. Elwert and E. Haug, Phys. Rev., 183, pp. 90–105, 1969 diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py index c967ae16b..605ef266d 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py @@ -1,6 +1,7 @@ import copy +from typing import Optional # Any, Dict import numpy as np @@ -15,12 +16,22 @@ from . import _xray_thin_target_integrated +TupleDict = tuple[dict] + + # #################################################### # #################################################### # DEFAULT # #################################################### +# Energy vectors +_E_E0_EV = np.logspace(3, 6, 51) +_E_PH_EV = np.linspace(1, 100, 25) * 1e3 +_THETA_PH = np.linspace(0, np.pi, 41) +_VERSION = 'BHE' + + # ANISOTROPY CASES _DCASES = { 0: { @@ -66,6 +77,8 @@ 'ms': 14, }, } + + # #################################################### # #################################################### # plot anisotropy @@ -73,26 +86,43 @@ def plot_xray_thin_d2cross_ei_anisotropy( - # compute - Z=None, + # target ion charge + Z: Optional[int] = None, + # Energy E_e0_eV=None, E_ph_eV=None, theta_ph=None, - per_energy_units=None, - version=None, - # hypergeometrc - ninf=None, - source=None, + # hypergeometric parameter + ninf: Optional[int] = None, + source: Optional[str] = None, + # output customization + per_energy_unit: Optional[str] = None, + # version + version: Optional[str] = None, # selected cases - dcases=None, + dcases: Optional[dict[int, dict]] = None, # plot - dax=None, - fontsize=None, + dax: Optional[dict] = None, + fontsize: Optional[int] = None, dplot_forbidden=None, dplot_peaking=None, dplot_thetamax=None, dplot_integ=None, -): +) -> TupleDict: + """ Compute and plot a (E_e0, E_ph) countour map of the d2cross section + + Where d2cross is the fully differentiated cross-section (d3cross), + integrated over one of the two the emission angle (dphi) + + Actually 3 overlayed contour plots with: + - integral of of the cross-section (over photon emission angle) + - angle of max cross-section + - peaking of the cross-section (std vs angle) + + Can overlay a few selected cases and plot them vs angle of emission + In normalized-linear and log scales + + """ # --------------- # check inputs @@ -100,6 +130,7 @@ def plot_xray_thin_d2cross_ei_anisotropy( ( E_e0_eV, E_ph_eV, theta_ph, + version, dcases, fontsize, dplot_forbidden, dplot_peaking, dplot_thetamax, dplot_integ, @@ -130,7 +161,7 @@ def plot_xray_thin_d2cross_ei_anisotropy( E_ph_eV=E_ph_eV[None, None, :], theta_ph=theta_ph[:, None, None], # output customization - per_energy_unit=per_energy_units, + per_energy_unit=per_energy_unit, # version version=version, # hypergeometric @@ -359,31 +390,33 @@ def _check_anisotropy( # E_e0_eV if E_e0_eV is None: - E_e0_eV = np.logspace(3, 6, 51) + E_e0_eV = _E_E0_EV E_e0_eV = ds._generic_check._check_flat1darray( E_e0_eV, 'E_e0_eV', dtype=float, sign='>0', + unique=True, ) # E_ph_eV if E_ph_eV is None: - E_ph_eV = np.linspace(1, 100, 25) * 1e3 + E_ph_eV = _E_PH_EV E_ph_eV = ds._generic_check._check_flat1darray( E_ph_eV, 'E_ph_eV', dtype=float, sign='>0', + unique=True, ) # theta_ph if theta_ph is None: - theta_ph = np.linspace(0, np.pi, 41) + theta_ph = _THETA_PH # version if version is None: - version = 'BHE' + version = _VERSION # ------------ # dcases @@ -462,6 +495,7 @@ def _check_anisotropy( return ( E_e0_eV, E_ph_eV, theta_ph, + version, dcases, fontsize, dplot_forbidden, dplot_peaking, dplot_thetamax, dplot_integ, From a2bceb5c75da8811cedc9220f6d567bc57910fb2 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Wed, 7 Jan 2026 19:01:11 +0000 Subject: [PATCH 02/17] [#1166] Cleanup --- .../_xray_thin_target_integrated_plot.py | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py index 605ef266d..279bfa5a4 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py @@ -107,7 +107,7 @@ def plot_xray_thin_d2cross_ei_anisotropy( dplot_forbidden=None, dplot_peaking=None, dplot_thetamax=None, - dplot_integ=None, + dplot_mean=None, ) -> TupleDict: """ Compute and plot a (E_e0, E_ph) countour map of the d2cross section @@ -133,7 +133,7 @@ def plot_xray_thin_d2cross_ei_anisotropy( version, dcases, fontsize, - dplot_forbidden, dplot_peaking, dplot_thetamax, dplot_integ, + dplot_forbidden, dplot_peaking, dplot_thetamax, dplot_mean, ) = _check_anisotropy( E_e0_eV=E_e0_eV, E_ph_eV=E_ph_eV, @@ -146,7 +146,7 @@ def plot_xray_thin_d2cross_ei_anisotropy( dplot_forbidden=dplot_forbidden, dplot_peaking=dplot_peaking, dplot_thetamax=dplot_thetamax, - dplot_integ=dplot_integ, + dplot_mean=dplot_mean, ) # --------------- @@ -193,20 +193,21 @@ def plot_xray_thin_d2cross_ei_anisotropy( for iv, (kk, vv) in enumerate(d2cross['cross'].items()): # compute integral and peaking - integ, peaking = _get_peaking( + mean, peaking = _get_peaking( vv['data'], theta_ph*180/np.pi, axis=0, ) + mean_units = vv['units'] # integral - if dplot_integ is not False: + if dplot_mean is not False: im0 = ax.contour( E_e0_eV * 1e-3, E_ph_eV * 1e-3, - np.log10(integ).T, - levels=dplot_integ['levels'], - colors=dplot_integ['colors'], + np.log10(mean).T, + levels=dplot_mean['levels'], + colors=dplot_mean['colors'], ) # clabels @@ -271,24 +272,27 @@ def plot_xray_thin_d2cross_ei_anisotropy( ax.add_patch(patch) # legend - lh = [ - mlines.Line2D( + lh = [] + if dplot_mean is not False: + lh.append(mlines.Line2D( [], [], - c=dplot_integ['colors'], - label='log10(integral)', - ), - mlines.Line2D( + c=dplot_mean['colors'], + label=f'log10() (log10({mean_units}))', + )) + if dplot_peaking is not False: + lh.append(mlines.Line2D( [], [], c=dplot_peaking['colors'], label='peaking (1/std)', - ), - mlines.Line2D( + )) + if dplot_thetamax is not False: + lh.append(mlines.Line2D( [], [], c=dplot_thetamax['colors'], label='theta_max (deg)', - ), - ] - ax.legend(handles=lh, loc='upper left') + )) + if len(lh) > 0: + ax.legend(handles=lh, loc='upper left') # add cases for ic, (kcase, vcase) in enumerate(dcases.items()): @@ -351,7 +355,7 @@ def plot_xray_thin_d2cross_ei_anisotropy( ax.set_xlim(0, 180) # normalized - kax = 'abs' + kax = 'log' if dax.get(kax) is not None: ax = dax[kax]['handle'] ax.legend(prop={'size': 12}) @@ -385,7 +389,7 @@ def _check_anisotropy( dplot_forbidden=None, dplot_peaking=None, dplot_thetamax=None, - dplot_integ=None, + dplot_mean=None, ): # E_e0_eV @@ -485,11 +489,11 @@ def _check_anisotropy( ddef, ) - # dplot_integ + # dplot_mean ddef = {'colors': 'g', 'levels': 20} - dplot_integ = _check_anisotropy_dplot( - dplot_integ, - 'dplot_integ', + dplot_mean = _check_anisotropy_dplot( + dplot_mean, + 'dplot_mean', ddef, ) @@ -498,7 +502,7 @@ def _check_anisotropy( version, dcases, fontsize, - dplot_forbidden, dplot_peaking, dplot_thetamax, dplot_integ, + dplot_forbidden, dplot_peaking, dplot_thetamax, dplot_mean, ) @@ -568,7 +572,7 @@ def _get_peaking(data, x, axis=None): x_avf = scpinteg.simpson(data_n * xf, x=x, axis=axis).reshape(shape_integ) std = np.sqrt(scpinteg.simpson(data_n * (xf - x_avf)**2, x=x, axis=axis)) - return integ, 1/std + return integ/180, 1/std # ############################################# @@ -584,13 +588,13 @@ def _get_axes_anisotropy( ): tit = ( - "Anisotropy" + "Thin-target Bremsstrahlung cross-section anisotropy" ) dmargin = { - 'left': 0.08, 'right': 0.95, - 'bottom': 0.06, 'top': 0.85, - 'wspace': 0.2, 'hspace': 0.40, + 'left': 0.06, 'right': 0.95, + 'bottom': 0.06, 'top': 0.90, + 'wspace': 0.20, 'hspace': 0.20, } fig = plt.figure(figsize=(15, 12)) @@ -659,11 +663,6 @@ def _get_axes_anisotropy( size=fontsize, fontweight='bold', ) - ax.set_ylabel( - r"$\frac{d^2\sigma_{ei}}{dkd\Omega_{ph}}$ ()", - size=fontsize, - fontweight='bold', - ) ax.set_title( "Absolute cross-section vs photon emission angle", size=fontsize, From 4d4c715dd7c07c8fdd4d3e5f4f221668012899ce Mon Sep 17 00:00:00 2001 From: dvezinet Date: Thu, 8 Jan 2026 21:20:13 +0000 Subject: [PATCH 03/17] [#1166] _xray_thin_target_integrated.py updated to load d2cross directly too --- .../electrons/emission/__init__.py | 1 + .../emission/_xray_thin_target_integrated.py | 537 +++++++++++++++--- .../_xray_thin_target_integrated_plot.py | 10 +- 3 files changed, 455 insertions(+), 93 deletions(-) diff --git a/tofu/physics_tools/electrons/emission/__init__.py b/tofu/physics_tools/electrons/emission/__init__.py index 5deac83c0..324733945 100644 --- a/tofu/physics_tools/electrons/emission/__init__.py +++ b/tofu/physics_tools/electrons/emission/__init__.py @@ -13,4 +13,5 @@ from ._xray_thin_target_integrated_plot import plot_xray_thin_d2cross_ei_anisotropy from ._xray_thin_target_integrated_dist import get_xray_thin_integ_dist from ._xray_thin_target_integrated_d2crossphi import get_d2cross_phi +from ._xray_thin_target_integrated_dist_responsivity_plot import plot_xray_thin_integ_dist_filter_anisotropy from ._xray_thin_target_integrated_dist_plot import plot_xray_thin_integ_dist diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py index 0c9e68c5e..774564980 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py @@ -1,7 +1,9 @@ import os -from typing import Any, Optional # Dict +import copy +import warnings +from typing import Optional # Dict, Any import numpy as np @@ -37,6 +39,45 @@ _NDPHI = 51 +# Default naming +_DSCALE = { + 12: 'T', + 9: 'G', + 6: 'M', + 3: 'k', + 0: '', + -3: 'm', + -6: 'u', + -9: 'n', +} + + +_DFORMAT = { + # energies + 'E_e0': { + 'data': None, + 'units': 'eV', + }, + 'E_ph': { + 'data': None, + 'units': 'eV', + }, + # angles + 'theta_ph': { + 'data': None, + 'units': 'rad', + }, + 'theta_e': { + 'data': None, + 'units': 'rad', + }, + 'dphi': { + 'data': None, + 'units': 'rad', + }, +} + + # #################################################### # #################################################### # main @@ -44,6 +85,8 @@ def get_xray_thin_d2cross_ei_integrated_thetae_dphi( + # optional input d2cross file + d2cross: Optional[str | dict] = None, # target ion charge Z: Optional[int] = None, # energies @@ -63,8 +106,17 @@ def get_xray_thin_d2cross_ei_integrated_thetae_dphi( # verb verb: Optional[bool] = None, verb_tab: Optional[str] = None, + # saving + save: Optional[bool] = None, + pfe_save: Optional[str] = None, + overwrite: Optional[bool] = None, ) -> dict: """ Compute d2cross, which is d3cross integrated over dphi + + Optionally loads / checks formatting of a pre-existing d2cross + - d2cross = str, should be a pfe to a local .npz + - d2cross = dict, will use as-is + """ # ------------ @@ -76,6 +128,7 @@ def get_xray_thin_d2cross_ei_integrated_thetae_dphi( nthetae, ndphi, shape, shape_theta_e, shape_dphi, verb, verb_tab, + save, pfe_save, overwrite, ) = _check( # inputs E_e0_eV=E_e0_eV, @@ -87,107 +140,36 @@ def get_xray_thin_d2cross_ei_integrated_thetae_dphi( # verb verb=verb, verb_tab=verb_tab, + # save + save=save, + pfe_save=pfe_save, + overwrite=overwrite, ) # ------------------ - # Derive angles + # compute # ------------------ - # E_e1_eV - E_e1_eV = E_e0_eV - E_ph_eV + if d2cross is None: - # angles - theta_e = np.pi * np.linspace(0, 1, nthetae) - dphi = np.pi * np.linspace(-1, 1, ndphi) - theta_ef = theta_e.reshape(shape_theta_e) - dphif = dphi.reshape(shape_dphi) + d2cross = _compute(**locals()) - # derived - sinte = np.sin(theta_ef) - - # ------------------ - # get d3cross - # ------------------ - - if verb >= 1: - msg = f"{verb_tab}Computing d3cross for shape {shape}... " - print(msg) - - d3cross = _xray_thin_target.get_xray_thin_d3cross_ei( - # inputs - Z=Z, - E_e0_eV=E_e0_eV[..., None, None], - E_e1_eV=E_e1_eV[..., None, None], - # directions - theta_ph=theta_ph[..., None, None], - theta_e=theta_ef, - dphi=dphif, - # hypergeometric parameter - ninf=ninf, - source=source, - # output customization - per_energy_unit=per_energy_unit, - # version - version=version, - # debug - debug=False, - ) + # optional save + if save is True: + _save( + d2cross, + pfe_save=pfe_save, + overwrite=overwrite, + verb=verb, + ) # ------------------ - # prepare output + # load # ------------------ - d2cross = { - # energies - 'E_e0': { - 'data': E_e0_eV, - 'units': 'eV', - }, - 'E_ph': { - 'data': E_ph_eV, - 'units': 'eV', - }, - # angles - 'theta_ph': { - 'data': theta_ph, - 'units': 'rad', - }, - 'theta_e': { - 'data': theta_e, - 'units': 'rad', - }, - 'dphi': { - 'data': dphi, - 'units': 'rad', - }, - # cross-section - 'cross': { - vv: { - 'data': np.full(shape, 0.), - 'units': asunits.Unit(vcross['units']) * asunits.Unit('sr'), - } - for vv, vcross in d3cross['cross'].items() - }, - } - - # ------------------ - # integrate - # ------------------ + else: - if verb >= 1: - msg = f"{verb_tab}Integrating..." - print(msg) - - for vv, vcross in d3cross['cross'].items(): - d2cross['cross'][vv]['data'][...] = scpinteg.simpson( - scpinteg.simpson( - vcross['data'] * sinte, - x=theta_e, - axis=-1, - ), - x=dphi, - axis=-1, - ) + d2cross = _load(d2cross) return d2cross @@ -209,6 +191,10 @@ def _check( # verb verb=None, verb_tab=None, + # saving + save=None, + pfe_save=None, + overwrite=None, ): # ----------- @@ -299,14 +285,385 @@ def _check( ) verb_tab = '\t'*verb_tab + # ----------- + # save + # ----------- + + savedef = pfe_save not in [None, False] + save = ds._generic_check._check_var( + save, 'save', + types=bool, + default=savedef, + ) + + # ----------- + # pfe_save + # ----------- + + if save is True: + if pfe_save is not None: + pfe_save = int(ds._generic_check._check_var( + pfe_save, 'pfe_save', + types=str, + )) + + try: + pfe_save = os.path.abspath(pfe_save) + except Exception as err: + msg = ( + "Arg 'pfe_save' must point to an valid path/file.ext\n" + f"Provided: {pfe_save}\n" + ) + raise Exception(msg) from err + + if not pfe_save.endswith('.npz'): + msg = ( + "Arg 'pfe_save' must point to an valid path/file.npz\n" + f"Provided: {pfe_save}\n" + ) + raise Exception(msg) + + else: + pfe_save = None + + # ----------- + # overwrite + # ----------- + + overwrite = ds._generic_check._check_var( + overwrite, 'overwrite', + types=bool, + default=False, + ) + return ( E_e0_eV, E_ph_eV, theta_ph, nthetae, ndphi, shape, shape_theta_e, shape_dphi, verb, verb_tab, + save, pfe_save, overwrite, ) +# #################################################### +# #################################################### +# compute +# #################################################### + + +def _compute( + Z=None, + E_e0_eV=None, + E_e1_eV=None, + E_ph_eV=None, + theta_ph=None, + # shapes + nthetae=None, + shape_theta_e=None, + theta_ef=None, + ndphi=None, + shape_dphi=None, + # parameters + ninf=None, + source=None, + version=None, + per_energy_unit=None, + # misc + shape=None, + verb=None, + verb_tab=None, + # unused + **kwdargs, +): + + # ------------------ + # get angles and shape + # ------------------ + + # E_e1_eV + E_e1_eV = E_e0_eV - E_ph_eV + + # angles + theta_e = np.pi * np.linspace(0, 1, nthetae) + dphi = np.pi * np.linspace(-1, 1, ndphi) + theta_ef = theta_e.reshape(shape_theta_e) + dphif = dphi.reshape(shape_dphi) + + # derived + sinte = np.sin(theta_ef) + + # ------------------ + # get d3cross + # ------------------ + + if verb >= 1: + msg = f"{verb_tab}Computing d3cross for shape {shape}... " + print(msg) + + d3cross = _xray_thin_target.get_xray_thin_d3cross_ei( + # inputs + Z=Z, + E_e0_eV=E_e0_eV[..., None, None], + E_e1_eV=E_e1_eV[..., None, None], + # directions + theta_ph=theta_ph[..., None, None], + theta_e=theta_ef, + dphi=dphif, + # hypergeometric parameter + ninf=ninf, + source=source, + # output customization + per_energy_unit=per_energy_unit, + # version + version=version, + # debug + debug=False, + ) + + # ------------------ + # prepare output + # ------------------ + + d2cross = copy.deepcopy(_DFORMAT) + for kk, vv in _DFORMAT.items(): + kv = f"{kk}_{vv['units']}" if vv['units'] == 'eV' else kk + d2cross[kk]['data'] = eval(kv) + + # cross-sections + d2cross['cross'] = { + vv: { + 'data': np.full(shape, 0.), + 'units': asunits.Unit(vcross['units']) * asunits.Unit('sr'), + } + for vv, vcross in d3cross['cross'].items() + } + + # ------------------ + # integrate + # ------------------ + + if verb >= 1: + msg = f"{verb_tab}Integrating..." + print(msg) + + for vv, vcross in d3cross['cross'].items(): + d2cross['cross'][vv]['data'][...] = scpinteg.trapezoid( + scpinteg.trapezoid( + vcross['data'] * sinte, + x=theta_e, + axis=-1, + ), + x=dphi, + axis=-1, + ) + + return d2cross + + +# #################################################### +# #################################################### +# save +# #################################################### + + +def _save( + d2cross=None, + pfe_save=None, + overwrite=None, + verb=None, +): + + # ---------- + # pfe_save + # ---------- + + if pfe_save is None: + + # extract sizes + ntheta = d2cross['theta_ph']['data'].size + Eph = _format_vect2str( + d2cross['E_ph']['data'], + base=d2cross['E_ph']['units'], + ) + Ee0 = _format_vect2str( + d2cross['E_e0']['data'], + base=d2cross['E_e0']['units'], + ) + + # extract boundaries + path = os.path.abspath(_PATH_HERE) + fname = f"d2cross_Ee0{Ee0}_Eph{Eph}_ntheta{ntheta}.npz" + pfe_save = os.path.join(path, f"{fname}.npz") + + # ---------- + # overwrite + # ---------- + + if os.path.isfile(pfe_save): + if overwrite is True: + if verb is True: + msg = f"Overwritting file {pfe_save}\n" + warnings.warn(msg) + else: + msg = ( + "File {pfe_save} already exists!\n" + "\t=> use overwrite=True to overwrite\n" + ) + raise Exception(msg) + + # ---------- + # save + # ---------- + + np.savez(pfe_save, **d2cross) + + # ---------- + # verb + # ---------- + + if verb is True: + msg = "Saved d2cross in:\n\t{pfe_save}\n" + print(msg) + + return + + +# #################################################### +# #################################################### +# Built str vector +# #################################################### + + +def _format_vect2str(vect, base='eV'): + + # ------------- + # extract + # ------------- + + v0 = vect.min() + v1 = vect.max() + nv = vect.size + + # ------------- + # test scale + # ------------- + + dlog = np.diff(np.log(vect)) + dlin = np.diff(vect) + islog = np.allclose(dlog, dlog[0]) + islin = np.allclose(dlin, dlin[0]) + + if islog is True: + scale = 'log' + elif islin is True: + scale = 'lin' + else: + scale = '' + + # ------------- + # format + # ------------- + + v0 = _format(v0, base=base) + v1 = _format(v1, base=base) + + return f"{v0}-{v1}-{nv}{scale}" + + +def _format(vv, base='eV'): + + ls = sorted(_DSCALE.keys()) + ind = np.searchsorted(ls, np.log10(vv), side='right') - 1 + key = ls[ind] + factor = 10**(-key) + + return f"{vv*factor:3.0f}{_DSCALE[key]}{base}".strip() + + +# #################################################### +# #################################################### +# load +# #################################################### + + +def _load( + d2cross=None, +): + + # --------- + # str + # --------- + + if isinstance(d2cross, str): + + if not (os.path.isfile(d2cross) and d2cross.endswith('.npz')): + msg = ( + "Arg 'd2cross', if a str, should be valid path/file.npz\n" + f"Provided: {d2cross}\n" + ) + raise Exception(msg) + + d2cross = dict(np.load(d2cross, allow_pickle=True)) + + # --------- + # dict + # --------- + + if not isinstance(d2cross, dict): + msg = "Arg 'd2cross' must be a dict!\nProvided: {type(d2cross)}\n" + raise Exception(msg) + + # ---------------- + # inner formatting + # ---------------- + + dfail = {} + for kk in _DFORMAT.keys(): + if not isinstance(d2cross.get(kk), dict): + dfail[kk] = 'not a key or value not a dict ()' + elif str(d2cross[kk].get('units')) != _DFORMAT[kk]['units']: + dfail[kk] = 'no or wrong units ()' + elif not isinstance(d2cross[kk]['data'], np.ndarray): + dfail[kk] = "data is not a np.ndarray (type(d2cross[kk]['data']))" + else: + dfail[kk] = 'ok' + + if any([vv != 'ok' for vv in dfail.values()]) > 0: + lstr = [f"\t- {kk}: {vv}" for kk, vv in dfail.items()] + msg = ( + "Arg 'd2cross' must be a dict of subdicts " + "{'data': np.ndarray, 'units': str}, with keys:\n" + + "\n".join(lstr) + ) + raise Exception(msg) + + # ---------------- + # cross formatting + # ---------------- + + lok = ['BHE', 'BH', 'EH'] + c0 = ( + isinstance(d2cross.get('cross'), dict) + and all([ + kk in lok + and isinstance(vv, dict) + and isinstance(vv.get('data'), np.ndarray) + and isinstance(vv.get('data'), str) + for kk, vv in d2cross['cross'].items() + ]) + ) + if not c0: + msg = ( + "Arg d2cross['cross'] must be a dict " + "with {'version': dict} subdict with:\n" + f"\t- 'version' in {lok}\n" + f"Provided:\n{d2cross.get('cross')}\n" + ) + raise Exception(msg) + + return d2cross + + # #################################################### # #################################################### # plot vs litterature diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py index 279bfa5a4..3705df786 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py @@ -13,7 +13,7 @@ import datastock as ds -from . import _xray_thin_target_integrated +from . import _xray_thin_target_integrated as _mod TupleDict = tuple[dict] @@ -86,6 +86,8 @@ def plot_xray_thin_d2cross_ei_anisotropy( + # optional input d2cross file + d2cross: Optional[str | dict] = None, # target ion charge Z: Optional[int] = None, # Energy @@ -153,8 +155,8 @@ def plot_xray_thin_d2cross_ei_anisotropy( # prepare data # --------------- - mod = _xray_thin_target_integrated - d2cross = mod.get_xray_thin_d2cross_ei_integrated_thetae_dphi( + d2cross = _mod.get_xray_thin_d2cross_ei_integrated_thetae_dphi( + d2cross=d2cross, # inputs Z=Z, E_e0_eV=E_e0_eV[None, :, None], @@ -182,6 +184,8 @@ def plot_xray_thin_d2cross_ei_anisotropy( fontsize=fontsize, ) + dax = ds._generic_check._check_dax(dax) + # --------------- # plot - map # --------------- From 4153bd6039e118a39a936ed24dde1dedd339413f Mon Sep 17 00:00:00 2001 From: dvezinet Date: Thu, 8 Jan 2026 22:52:56 +0000 Subject: [PATCH 04/17] [#1166] operational for basics --- .../electrons/emission/_xray_thin_target_integrated_plot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py index 3705df786..f3d66b76a 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py @@ -455,6 +455,8 @@ def _check_anisotropy( + f"{ee0*1e-3:3.0f} / {eph*1e-3:3.0f} keV = " + f"{round(ee0 / eph, ndigits=1)}" ) + else: + dcases = {} # ------------ # plotting From c6c3eca744b343cd420e81a8437180789f1b3693 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Thu, 8 Jan 2026 22:53:12 +0000 Subject: [PATCH 05/17] [#1166] operational for basics --- ...arget_integrated_dist_responsivity_plot.py | 427 ++++++++++++++++++ 1 file changed, 427 insertions(+) create mode 100644 tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py new file mode 100644 index 000000000..c6b93ac8f --- /dev/null +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py @@ -0,0 +1,427 @@ + + +import copy +from typing import Optional # Any, Dict + + +import numpy as np +import astropy.units as asunits +import matplotlib.pyplot as plt +# import matplotlib.lines as mlines +import matplotlib.gridspec as gridspec +import datastock as ds + + +# from . import _xray_thin_target_integrated as _mod +from . import _xray_thin_target_integrated_plot as _mod_plot +# from ..distribution import get_distribution + + +TupleDict = tuple[dict] + + +# #################################################### +# #################################################### +# DEFAULT +# #################################################### + + +# Energy vectors +_E_E0_EV = np.logspace(3, 6, 51) +_E_PH_EV = np.linspace(1, 100, 25) * 1e3 +_THETA_PH = np.linspace(0, np.pi, 41) +_VERSION = 'BHE' + + +# DCASES +_DCASES = { + 'cvd no filter maxwell': { + 'E_ph': {}, + 'responsivity': {}, + 'dist': {}, + 'E_e0': {}, + }, +} + + +# #################################################### +# #################################################### +# plot integrated filtered anisotropy +# #################################################### + + +def plot_xray_thin_integ_dist_filter_anisotropy( + # optional input d2cross file + d2cross: Optional[str | dict] = None, + # target ion charge + Z: Optional[int] = None, + # Energy + E_e0_eV=None, + E_ph_eV=None, + theta_ph=None, + # hypergeometric parameter + ninf: Optional[int] = None, + source: Optional[str] = None, + # output customization + per_energy_unit: Optional[str] = None, + # version + version: Optional[str] = None, + # selected cases + dcases: Optional[dict[int, dict]] = None, + # plot + dax: Optional[dict] = None, + fs: Optional[tuple] = None, + fontsize: Optional[int] = None, + dplot_forbidden=None, + dplot_peaking=None, + dplot_thetamax=None, + dplot_mean=None, +) -> TupleDict: + """ Compute and plot a (E_e0, E_ph) countour map of the d2cross section + + Where d2cross is the fully differentiated cross-section (d3cross), + integrated over one of the two the emission angle (dphi) + + Actually 3 overlayed contour plots with: + - integral of of the cross-section (over photon emission angle) + - angle of max cross-section + - peaking of the cross-section (std vs angle) + + Can overlay a few selected cases and plot them vs angle of emission + In normalized-linear and log scales + + """ + + # --------------- + # check inputs + # --------------- + + dcases, fs, fontsize = _check( + dcases=dcases, + fs=fs, + fontsize=fontsize, + ) + + # --------------- + # prepare data + # --------------- + + # -------------- + # prepare axes + # -------------- + + if dax is None: + dax = _dax( + Z=Z, + version=version, + fs=fs, + fontsize=fontsize, + ) + + dax = ds._generic_check._check_dax(dax) + + # ------------------- + # plot cross-section + # ------------------- + + dax_cross, d2cross_cross = _mod_plot.plot_xray_thin_d2cross_ei_anisotropy( + # optional input d2cross file + d2cross=d2cross, + # target ion charge + Z=Z, + # Energy + E_e0_eV=E_e0_eV, + E_ph_eV=E_ph_eV, + theta_ph=theta_ph, + # hypergeometric parameter + ninf=ninf, + source=source, + # output customization + per_energy_unit=per_energy_unit, + # version + version=version, + # selected cases + dcases=False, + # plot + dax=dax, + dplot_forbidden=dplot_forbidden, + dplot_peaking=dplot_peaking, + dplot_thetamax=dplot_thetamax, + dplot_mean=dplot_mean, + ) + + # ------------------- + # plot responsivity + # ------------------- + + kax = 'responsivity' + if dax.get(kax) is not None: + ax = dax[kax]['handle'] + + for kk, vv in dcases.items(): + + l0, = ax.plot( + vv['responsivity']['data'], + vv['E_ph']['data']*1e-3, + c=vv.get('color'), + ls=vv.get('ls', '-'), + marker=vv.get('marker'), + lw=vv.get('lw', 1.), + label=kk, + ) + dcases[kk]['color'] = l0.get_color() + + ax.set_ylabel( + vv['responsivity']['units'], + fontsize=fontsize, + fontweight='bold', + ) + + # ------------------- + # plot distribution + # ------------------- + + kax = 'dist' + if dax.get(kax) is not None: + ax = dax[kax]['handle'] + + for kk, vv in dcases.items(): + + ax.semilogy( + vv['E_e0']['data']*1e-3, + vv['dist']['data'], + c=vv['color'], + ls=vv.get('ls', '-'), + marker=vv.get('marker'), + lw=vv.get('lw', 1.), + label=kk, + ) + + ax.set_ylabel( + vv['dist']['units'], + fontsize=fontsize, + fontweight='bold', + ) + + return dax + + +# ############################################# +# ############################################# +# Axes for anisotropy +# ############################################# + + +def _check( + dcases=None, + fs=None, + fontsize=None, +): + + # ------------ + # dcases + # ------------ + + ddef = copy.deepcopy(_DCASES) + if dcases in [None, False]: + dcases = {} + else: + for k0, v0 in dcases.items(): + dcases[k0] = _check_case( + v0, + f"dcases['{k0}']", + ddef[list(ddef.keys())[0]], + ) + + # ------------ + # fs + # ------------ + + if fs is None: + fs = (15, 12) + + # ------------ + # fontsize + # ------------ + + fontsize = ds._generic_check._check_var( + fontsize, 'fontsize', + types=int, + default=14, + sign='>0', + ) + + return dcases, fs, fontsize + + +def _check_case( + case=None, + key=None, + ddef=None, +): + + # -------------- + # general structure + # -------------- + + dfail = {} + lok = list(ddef.keys()) + ltunits = (str, asunits.Unit, asunits.CompositeUnit) + for kk in lok: + if not isinstance(case.get(kk), dict): + typ = type(case.get(kk)) + dfail[kk] = f'absent or not a dict ({typ})' + elif not isinstance(case[kk].get('data'), np.ndarray): + typ = type(case[kk].get('data')) + dfail[kk] = f'data key not a np.ndarray ({typ})' + elif not isinstance(case[kk].get('units'), ltunits): + typ = type(case[kk].get('units')) + dfail[kk] = f"units not a str ({typ})" + else: + dfail[kk] = 'ok' + + if any([vv != 'ok' for vv in dfail.values()]): + lstr = [f"\t- {kk}: {vv}" for kk, vv in dfail.items()] + msg = ( + f"Arg {key} must be a dict with keys {lok}, " + "where each is a {'data': np.ndarray, 'units': str} subdict!\n" + + "\n".join(lstr) + ) + raise Exception(msg) + + # -------------- + # shape consistency + # -------------- + + shape_Eph = case['E_ph']['data'].shape + shape_resp = case['responsivity']['data'].shape + if shape_Eph != shape_resp: + msg = ( + "The 2 fields below must have the same shape:\n" + f"{key}['E_ph']['data'].shape = {shape_Eph}\n" + f"{key}['responsivity']['data'].shape = {shape_resp}\n" + ) + raise Exception(msg) + + shape_Ee0 = case['E_e0']['data'].shape + shape_dist = case['dist']['data'].shape + if shape_Ee0 != shape_dist: + msg = ( + "The 2 fields below must have the same shape:\n" + f"{key}['E_e0']['data'].shape = {shape_Ee0}\n" + f"{key}['dist']['data'].shape = {shape_dist}\n" + ) + raise Exception(msg) + + return case + + +# ############################################# +# ############################################# +# Axes for anisotropy +# ############################################# + + +def _dax( + Z=None, + version=None, + fs=None, + fontsize=None, +): + + # -------------- + # prepare figure + # -------------- + + tit = ( + "Thin-target Bremsstrahlung emission anisotropy" + ) + + dmargin = { + 'left': 0.06, 'right': 0.95, + 'bottom': 0.06, 'top': 0.90, + 'wspace': 0.20, 'hspace': 0.20, + } + + fig = plt.figure(figsize=(15, 12)) + fig.suptitle(tit, size=fontsize+2, fontweight='bold') + + nh0, nh1, nh2 = 2, 5, 4 + nv0, nv1, nv2 = 3, 1, 2 + gs = gridspec.GridSpec( + ncols=nh0+nh1+nh2 + 1, + nrows=nv0 + nv1, + **dmargin, + ) + dax = {} + + # -------------- + # prepare axes + # -------------- + + # -------------- + # ax - isolines + + ax = fig.add_subplot(gs[:nv0, nh0:nh0+nh1], xscale='log') + ax.set_title( + r"$d^2\sigma(E_{e0}, E_{ph}, \theta_{ph}, Z)$" + + f"\n Z = {Z}, version = {version}", + size=fontsize, + fontweight='bold', + ) + + # store + dax['map'] = {'handle': ax, 'type': 'isolines'} + + # -------------- + # ax - responsivity + + ax = fig.add_subplot(gs[:nv0, :nh0], sharey=dax['map']['handle']) + ax.set_title( + "responsivity", + size=fontsize, + fontweight='bold', + ) + ax.set_ylabel( + r"$E_{ph}$ (keV)", + size=fontsize, + fontweight='bold', + ) + ax.grid(True) + + # store + dax['responsivity'] = {'handle': ax, 'type': 'isolines'} + + # -------------- + # ax - dist + + ax = fig.add_subplot(gs[nv0:, nh0:nh0 + nh1], sharex=dax['map']['handle']) + ax.set_xlabel( + r"$E_{e,0}$ (keV)", + size=fontsize, + fontweight='bold', + ) + ax.grid(True) + + # store + dax['dist'] = {'handle': ax, 'type': 'isolines'} + + # -------------- + # ax - theta + + ax = fig.add_subplot(gs[:nv2, nh0 + nh1 + 1:]) + ax.set_xlabel( + r"$\theta_{ph}^B$ (deg)", + size=fontsize, + fontweight='bold', + ) + ax.set_ylabel( + "emiss (ph/sr/s/m3)", + size=fontsize, + fontweight='bold', + ) + + # store + dax['theta'] = {'handle': ax, 'type': 'isolines'} + + return dax From da008866be893e1b8d6b53b06fa4dfe7b652fa3f Mon Sep 17 00:00:00 2001 From: dvezinet Date: Fri, 9 Jan 2026 17:21:17 +0000 Subject: [PATCH 06/17] [#1166] minor cleanup --- .../_xray_thin_target_integrated_dist_responsivity_plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py index c6b93ac8f..ef4862d0e 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py @@ -171,7 +171,7 @@ def plot_xray_thin_integ_dist_filter_anisotropy( ) dcases[kk]['color'] = l0.get_color() - ax.set_ylabel( + ax.set_xlabel( vv['responsivity']['units'], fontsize=fontsize, fontweight='bold', From 86e2d48f7d09eb03b3e6b29c29a5eb5fa2546323 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Fri, 9 Jan 2026 17:22:13 +0000 Subject: [PATCH 07/17] [#1166] d2cross loop on E_e0, E_ph and theta_ph => too slow --- .../emission/_xray_thin_target_integrated.py | 128 +++++++++--------- 1 file changed, 66 insertions(+), 62 deletions(-) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py index 774564980..264b048e8 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py @@ -126,7 +126,7 @@ def get_xray_thin_d2cross_ei_integrated_thetae_dphi( ( E_e0_eV, E_ph_eV, theta_ph, nthetae, ndphi, - shape, shape_theta_e, shape_dphi, + shape, verb, verb_tab, save, pfe_save, overwrite, ) = _check( @@ -233,13 +233,9 @@ def _check( theta_ph=theta_ph, ) - # ----------- - # shapes - # ----------- - - shape = np.broadcast_shapes(E_e0_eV.shape, E_ph_eV.shape, theta_ph.shape) - shape_theta_e = (1,) * (len(shape)+1) + (-1,) - shape_dphi = (1,) * len(shape) + (-1, 1) + E_e0_eV, E_ph_eV, theta_ph = np.broadcast_arrays( + E_e0_eV, E_ph_eV, theta_ph, + ) # ----------- # integers @@ -339,7 +335,7 @@ def _check( return ( E_e0_eV, E_ph_eV, theta_ph, nthetae, ndphi, - shape, shape_theta_e, shape_dphi, + shape, verb, verb_tab, save, pfe_save, overwrite, ) @@ -359,10 +355,7 @@ def _compute( theta_ph=None, # shapes nthetae=None, - shape_theta_e=None, - theta_ef=None, ndphi=None, - shape_dphi=None, # parameters ninf=None, source=None, @@ -386,40 +379,12 @@ def _compute( # angles theta_e = np.pi * np.linspace(0, 1, nthetae) dphi = np.pi * np.linspace(-1, 1, ndphi) - theta_ef = theta_e.reshape(shape_theta_e) - dphif = dphi.reshape(shape_dphi) + theta_ef = np.broadcast_to(theta_e[None, :], (ndphi, nthetae)) + dphif = np.broadcast_to(dphi[:, None], (ndphi, nthetae)) # derived sinte = np.sin(theta_ef) - # ------------------ - # get d3cross - # ------------------ - - if verb >= 1: - msg = f"{verb_tab}Computing d3cross for shape {shape}... " - print(msg) - - d3cross = _xray_thin_target.get_xray_thin_d3cross_ei( - # inputs - Z=Z, - E_e0_eV=E_e0_eV[..., None, None], - E_e1_eV=E_e1_eV[..., None, None], - # directions - theta_ph=theta_ph[..., None, None], - theta_e=theta_ef, - dphi=dphif, - # hypergeometric parameter - ninf=ninf, - source=source, - # output customization - per_energy_unit=per_energy_unit, - # version - version=version, - # debug - debug=False, - ) - # ------------------ # prepare output # ------------------ @@ -429,34 +394,73 @@ def _compute( kv = f"{kk}_{vv['units']}" if vv['units'] == 'eV' else kk d2cross[kk]['data'] = eval(kv) - # cross-sections - d2cross['cross'] = { - vv: { - 'data': np.full(shape, 0.), - 'units': asunits.Unit(vcross['units']) * asunits.Unit('sr'), - } - for vv, vcross in d3cross['cross'].items() - } - # ------------------ - # integrate + # get d3cross # ------------------ if verb >= 1: - msg = f"{verb_tab}Integrating..." + msg = f"{verb_tab}Computing d3cross for shape {shape}... " print(msg) - for vv, vcross in d3cross['cross'].items(): - d2cross['cross'][vv]['data'][...] = scpinteg.trapezoid( - scpinteg.trapezoid( - vcross['data'] * sinte, - x=theta_e, - axis=-1, - ), - x=dphi, - axis=-1, + # ------------------------------- + # loop on all but phi and theta_e + + size = np.prod(shape) + for ii, ind in enumerate(np.ndindex(shape)): + + # verb + if verb >= 2: + end = '\n' if ii == size - 1 else '\r' + msg = f"\t{ii+1} / {size}, index {ind} / {shape}" + print(msg, end=end) + + # sli + sli = ind + (None, None) + + # d3cross + d3cross = _xray_thin_target.get_xray_thin_d3cross_ei( + # inputs + Z=Z, + E_e0_eV=E_e0_eV[sli], + E_e1_eV=E_e1_eV[sli], + # directions + theta_ph=theta_ph[sli], + theta_e=theta_ef, + dphi=dphif, + # hypergeometric parameter + ninf=ninf, + source=source, + # output customization + per_energy_unit=per_energy_unit, + # version + version=version, + # debug + debug=False, ) + if ii == 0: + srunits = asunits.Unit('sr') + # cross-sections + d2cross['cross'] = { + vv: { + 'data': np.full(shape, 0.), + 'units': asunits.Unit(vcross['units']) * srunits, + } + for vv, vcross in d3cross['cross'].items() + } + + # integrate of theta_e + for vv, vcross in d3cross['cross'].items(): + d2cross['cross'][vv]['data'][ind] = scpinteg.trapezoid( + scpinteg.trapezoid( + vcross['data'] * sinte, + x=theta_e, + axis=-1, + ), + x=dphi, + axis=-1, + ) + return d2cross From c1b3b1b8103b5f91cae14df52c1bb88618e8a932 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Fri, 9 Jan 2026 19:33:02 +0000 Subject: [PATCH 08/17] [#1166] d2cross now looping over just the largest dimension (no angle) --- .../emission/_xray_thin_target_integrated.py | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py index 264b048e8..1023bf112 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py @@ -379,8 +379,19 @@ def _compute( # angles theta_e = np.pi * np.linspace(0, 1, nthetae) dphi = np.pi * np.linspace(-1, 1, ndphi) - theta_ef = np.broadcast_to(theta_e[None, :], (ndphi, nthetae)) - dphif = np.broadcast_to(dphi[:, None], (ndphi, nthetae)) + + # --------------------- + # determine how to loop + # --------------------- + + # loop on largest dimension + iloop = np.argmax(shape) + sli = np.array([slice(None)]*len(shape) + [None, None]) + slistr = [':'] * (len(shape) + 2) + + sli_ang = (None,) * (len(shape) - 1) + (slice(None),)*2 + theta_ef = theta_e[None, :][sli_ang] + dphif = dphi[:, None][sli_ang] # derived sinte = np.sin(theta_ef) @@ -405,26 +416,28 @@ def _compute( # ------------------------------- # loop on all but phi and theta_e - size = np.prod(shape) - for ii, ind in enumerate(np.ndindex(shape)): + size = shape[iloop] + for ii in range(size): + + # sli + sli[iloop] = ii + slit = tuple(sli) # verb if verb >= 2: + slistr[iloop] = str(ii) end = '\n' if ii == size - 1 else '\r' - msg = f"\t{ii+1} / {size}, index {ind} / {shape}" + msg = f"\t{ii+1} / {size}, index ({', '.join(slistr)}) / {shape}" print(msg, end=end) - # sli - sli = ind + (None, None) - # d3cross d3cross = _xray_thin_target.get_xray_thin_d3cross_ei( # inputs Z=Z, - E_e0_eV=E_e0_eV[sli], - E_e1_eV=E_e1_eV[sli], + E_e0_eV=E_e0_eV[slit], + E_e1_eV=E_e1_eV[slit], # directions - theta_ph=theta_ph[sli], + theta_ph=theta_ph[slit], theta_e=theta_ef, dphi=dphif, # hypergeometric parameter @@ -451,7 +464,7 @@ def _compute( # integrate of theta_e for vv, vcross in d3cross['cross'].items(): - d2cross['cross'][vv]['data'][ind] = scpinteg.trapezoid( + d2cross['cross'][vv]['data'][slit[:-2]] = scpinteg.trapezoid( scpinteg.trapezoid( vcross['data'] * sinte, x=theta_e, From 75c8262394eefc3f21f6306f9b3f923b0bbe9bc9 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Fri, 9 Jan 2026 19:33:26 +0000 Subject: [PATCH 09/17] [#1166] d3cross trying minor optimization --- .../electrons/emission/_xray_thin_target.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target.py b/tofu/physics_tools/electrons/emission/_xray_thin_target.py index 0b286a8a5..683868ac3 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target.py @@ -897,7 +897,13 @@ def _angle_dependent_internediates( sintp = np.sin(theta_ph) cosdphi = np.cos(dphi) - cossindphi = costp*coste + sintp*sinte*cosdphi + costpcoste = costp * coste + sintpsintecosdphi = sintp * sinte * cosdphi + + cossindphi = costpcoste + sintpsintecosdphi + + sintecostp = sinte * costp + costesintp = coste * sintp # ------------- # Vectors / scalar product @@ -905,15 +911,15 @@ def _angle_dependent_internediates( # scalar sca_kp0 = kk * p0 * costp - sca_p01 = p1 * p0 * coste + sca_p01 = p0 * p1 * coste sca_kp1 = kk * p1 * cossindphi # vect{q} = vect{p0 - p1 - k} q2 = ( p0**2 + p1**2 + k2 - - 2.*p0*p1*coste - - 2.*p0*kk*costp - + 2.*p1*kk*cossindphi + - 2. * sca_p01 + - 2. * sca_kp0 + + 2. * sca_kp1 ) # ------------- @@ -951,9 +957,9 @@ def _angle_dependent_internediates( # + (sinte*sintp)**2 * sin(phi_p - phi_e)**2 # ) eta12 = p12 * ( - (sinte*costp)**2 - + (coste*sintp)**2 - - 2*(coste*costp)*(sinte*sintp*cosdphi) + sintecostp**2 + + costesintp**2 + - 2 * costpcoste * sintpsintecosdphi + (sinte*sintp)**2 * (1 - cosdphi**2) ) @@ -972,7 +978,7 @@ def _angle_dependent_internediates( # coste*sintp*(sinpp**2 + cospp**2) # - sinte*costp*(sinpe*sinpp + cospe*cospp) # ) - sca_eta01 = p0 * p1 * sintp * (coste*sintp - sinte*costp*cosdphi) + sca_eta01 = p0 * p1 * sintp * (costesintp - sintecostp * cosdphi) # --------------- # Intermediates 1 From 9fa3adc695e585a946d118b6bd9fe75f5403dc94 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Fri, 9 Jan 2026 22:24:03 +0000 Subject: [PATCH 10/17] [#1166] plot() of d2cross and d2cross filtered now take custom x/y scales and verb --- ...arget_integrated_dist_responsivity_plot.py | 108 ++++++++++++++++-- .../_xray_thin_target_integrated_plot.py | 93 +++++++++++---- 2 files changed, 169 insertions(+), 32 deletions(-) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py index ef4862d0e..e50590e8d 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py @@ -33,6 +33,16 @@ _VERSION = 'BHE' +# DSCALES +_DSCALES = { + 'E_ph': 'log', + 'E_e0': 'log', + 'theta': 'linear', + 'dist': 'log', + 'resp': 'log', +} + + # DCASES _DCASES = { 'cvd no filter maxwell': { @@ -68,10 +78,17 @@ def plot_xray_thin_integ_dist_filter_anisotropy( version: Optional[str] = None, # selected cases dcases: Optional[dict[int, dict]] = None, + # verb + verb: Optional[bool] = None, # plot dax: Optional[dict] = None, fs: Optional[tuple] = None, fontsize: Optional[int] = None, + E_e0_scale: Optional[str] = None, + E_ph_scale: Optional[str] = None, + dist_scale: Optional[str] = None, + resp_scale: Optional[str] = None, + theta_scale: Optional[str] = None, dplot_forbidden=None, dplot_peaking=None, dplot_thetamax=None, @@ -96,11 +113,7 @@ def plot_xray_thin_integ_dist_filter_anisotropy( # check inputs # --------------- - dcases, fs, fontsize = _check( - dcases=dcases, - fs=fs, - fontsize=fontsize, - ) + dcases, dscales, fs, fontsize = _check(**locals()) # --------------- # prepare data @@ -116,6 +129,7 @@ def plot_xray_thin_integ_dist_filter_anisotropy( version=version, fs=fs, fontsize=fontsize, + dscales=dscales, ) dax = ds._generic_check._check_dax(dax) @@ -142,6 +156,8 @@ def plot_xray_thin_integ_dist_filter_anisotropy( version=version, # selected cases dcases=False, + # verb + verb=verb, # plot dax=dax, dplot_forbidden=dplot_forbidden, @@ -216,6 +232,13 @@ def _check( dcases=None, fs=None, fontsize=None, + E_e0_scale=None, + E_ph_scale=None, + dist_scale=None, + resp_scale=None, + theta_scale=None, + # unused + **kwdargs, ): # ------------ @@ -251,7 +274,29 @@ def _check( sign='>0', ) - return dcases, fs, fontsize + # ------------ + # scales + # ------------ + + dscales = { + 'E_ph': E_ph_scale, + 'E_e0': E_e0_scale, + 'theta': theta_scale, + 'dist': dist_scale, + 'resp': resp_scale, + } + + for kk, vv in dscales.items(): + dscales[kk] = ds._generic_check._check_var( + vv, f'{kk}_scale', + types=str, + allowed=['log', 'linear'], + default=_DSCALES[kk], + ) + + return ( + dcases, dscales, fs, fontsize, + ) def _check_case( @@ -327,6 +372,7 @@ def _dax( version=None, fs=None, fontsize=None, + dscales=None, ): # -------------- @@ -362,7 +408,11 @@ def _dax( # -------------- # ax - isolines - ax = fig.add_subplot(gs[:nv0, nh0:nh0+nh1], xscale='log') + ax = fig.add_subplot( + gs[:nv0, nh0:nh0+nh1], + xscale=dscales['E_e0'], + yscale=dscales['E_ph'], + ) ax.set_title( r"$d^2\sigma(E_{e0}, E_{ph}, \theta_{ph}, Z)$" + f"\n Z = {Z}, version = {version}", @@ -376,7 +426,11 @@ def _dax( # -------------- # ax - responsivity - ax = fig.add_subplot(gs[:nv0, :nh0], sharey=dax['map']['handle']) + ax = fig.add_subplot( + gs[:nv0, :nh0], + sharey=dax['map']['handle'], + xscale=dscales['resp'], + ) ax.set_title( "responsivity", size=fontsize, @@ -395,7 +449,11 @@ def _dax( # -------------- # ax - dist - ax = fig.add_subplot(gs[nv0:, nh0:nh0 + nh1], sharex=dax['map']['handle']) + ax = fig.add_subplot( + gs[nv0:, nh0:nh0 + nh1], + sharex=dax['map']['handle'], + yscale=dscales['dist'], + ) ax.set_xlabel( r"$E_{e,0}$ (keV)", size=fontsize, @@ -407,9 +465,35 @@ def _dax( dax['dist'] = {'handle': ax, 'type': 'isolines'} # -------------- - # ax - theta + # ax - theta - norm + + ax = fig.add_subplot( + gs[:nv2, nh0 + nh1 + 1:], + xscale=dscales['theta'], + yscale='linear', + ) + ax.set_xlabel( + r"$\theta_{ph}^B$ (deg)", + size=fontsize, + fontweight='bold', + ) + ax.set_ylabel( + "emiss (ph/sr/s/m3)", + size=fontsize, + fontweight='bold', + ) + + # store + dax['theta_norm'] = {'handle': ax, 'type': 'isolines'} + + # -------------- + # ax - theta - abs - ax = fig.add_subplot(gs[:nv2, nh0 + nh1 + 1:]) + ax = fig.add_subplot( + gs[nv2:, nh0 + nh1 + 1:], + sharex=dax['theta_norm']['handle'], + yscale='log', + ) ax.set_xlabel( r"$\theta_{ph}^B$ (deg)", size=fontsize, @@ -422,6 +506,6 @@ def _dax( ) # store - dax['theta'] = {'handle': ax, 'type': 'isolines'} + dax['theta_abs'] = {'handle': ax, 'type': 'isolines'} return dax diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py index f3d66b76a..289485b0a 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py @@ -32,6 +32,14 @@ _VERSION = 'BHE' +# DSCALES +_DSCALES = { + 'E_ph': 'linear', + 'E_e0': 'log', + 'theta': 'linear', +} + + # ANISOTROPY CASES _DCASES = { 0: { @@ -103,9 +111,14 @@ def plot_xray_thin_d2cross_ei_anisotropy( version: Optional[str] = None, # selected cases dcases: Optional[dict[int, dict]] = None, + # verb + verb: Optional[bool] = None, # plot dax: Optional[dict] = None, fontsize: Optional[int] = None, + E_e0_scale: Optional[str] = None, + E_ph_scale: Optional[str] = None, + theta_scale: Optional[str] = None, dplot_forbidden=None, dplot_peaking=None, dplot_thetamax=None, @@ -134,22 +147,11 @@ def plot_xray_thin_d2cross_ei_anisotropy( E_e0_eV, E_ph_eV, theta_ph, version, dcases, + dscales, + verb, fontsize, dplot_forbidden, dplot_peaking, dplot_thetamax, dplot_mean, - ) = _check_anisotropy( - E_e0_eV=E_e0_eV, - E_ph_eV=E_ph_eV, - theta_ph=theta_ph, - version=version, - # selected cases - dcases=dcases, - # plotting - fontsize=fontsize, - dplot_forbidden=dplot_forbidden, - dplot_peaking=dplot_peaking, - dplot_thetamax=dplot_thetamax, - dplot_mean=dplot_mean, - ) + ) = _check_anisotropy(**locals()) # --------------- # prepare data @@ -170,7 +172,7 @@ def plot_xray_thin_d2cross_ei_anisotropy( ninf=ninf, source=source, # verb - verb=False, + verb=verb, ) # -------------- @@ -178,10 +180,11 @@ def plot_xray_thin_d2cross_ei_anisotropy( # -------------- if dax is None: - dax = _get_axes_anisotropy( + dax = _dax( Z=Z, version=version, fontsize=fontsize, + dscales=dscales, ) dax = ds._generic_check._check_dax(dax) @@ -388,12 +391,20 @@ def _check_anisotropy( version=None, # selected cases dcases=None, + # verb + verb=None, + # scales + E_e0_scale=None, + E_ph_scale=None, + theta_scale=None, # plotting fontsize=None, dplot_forbidden=None, dplot_peaking=None, dplot_thetamax=None, dplot_mean=None, + # unused + **kwdargs, ): # E_e0_eV @@ -458,6 +469,16 @@ def _check_anisotropy( else: dcases = {} + # ----------- + # verb + # ----------- + + verb = ds._generic_check._check_var( + verb, 'verb', + types=(bool, int), + default=False, + ) + # ------------ # plotting # ------------ @@ -503,10 +524,30 @@ def _check_anisotropy( ddef, ) + # ------------ + # scales + # ------------ + + dscales = { + 'E_ph': E_ph_scale, + 'E_e0': E_e0_scale, + 'theta': theta_scale, + } + + for kk, vv in dscales.items(): + dscales[kk] = ds._generic_check._check_var( + vv, f'{kk}_scale', + types=str, + allowed=['log', 'linear'], + default=_DSCALES[kk], + ) + return ( E_e0_eV, E_ph_eV, theta_ph, version, dcases, + dscales, + verb, fontsize, dplot_forbidden, dplot_peaking, dplot_thetamax, dplot_mean, ) @@ -587,10 +628,12 @@ def _get_peaking(data, x, axis=None): # ############################################# -def _get_axes_anisotropy( +def _dax( Z=None, version=None, fontsize=None, + dax=None, + dscales=None, ): tit = ( @@ -616,7 +659,11 @@ def _get_axes_anisotropy( # -------------- # ax - isolines - ax = fig.add_subplot(gs[:, 0], xscale='log') + ax = fig.add_subplot( + gs[:, 0], + xscale=dscales['E_e0'], + yscale=dscales['E_ph'], + ) ax.set_xlabel( r"$E_{e,0}$ (keV)", size=fontsize, @@ -640,7 +687,10 @@ def _get_axes_anisotropy( # -------------- # ax - norm - ax = fig.add_subplot(gs[0, 1]) + ax = fig.add_subplot( + gs[0, 1], + xscale=dscales['theta'], + ) ax.set_xlabel( r"$\theta_{ph}$ (deg)", size=fontsize, @@ -663,7 +713,10 @@ def _get_axes_anisotropy( # -------------- # ax - log - ax = fig.add_subplot(gs[1, 1], sharex=dax['norm']['handle']) + ax = fig.add_subplot( + gs[1, 1], + sharex=dax['norm']['handle'], + ) ax.set_xlabel( r"$\theta_{ph}$ (deg)", size=fontsize, From e854e797b0417119036687e04ebcd53eee7a34e6 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Fri, 9 Jan 2026 22:24:33 +0000 Subject: [PATCH 11/17] [#1166] default version now at top of file --- tofu/physics_tools/electrons/emission/_xray_thin_target.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target.py b/tofu/physics_tools/electrons/emission/_xray_thin_target.py index 683868ac3..215cea37a 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target.py @@ -25,6 +25,10 @@ _PATH_HERE = os.path.dirname(__file__) +# version +_VERSION = 'EH' # most accurate, but slow + + # #################################################### # #################################################### # Differential cross-section @@ -360,7 +364,7 @@ def _check_cross( # ------------ if version is None: - version = 'EH' + version = _VERSION if isinstance(version, str): version = [version] From 8cbc7d9c9e2d0343b8456d8c82a158a868550cfe Mon Sep 17 00:00:00 2001 From: dvezinet Date: Fri, 9 Jan 2026 22:25:06 +0000 Subject: [PATCH 12/17] [#1166] Better d2cross saving --- .../emission/_xray_thin_target_integrated.py | 106 ++++++++++-------- 1 file changed, 58 insertions(+), 48 deletions(-) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py index 1023bf112..4e5a54f7c 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py @@ -38,6 +38,9 @@ _NTHETAE = 31 _NDPHI = 51 +# VERSION +_VERSION = 'BHE' # good compromise + # Default naming _DSCALE = { @@ -126,25 +129,11 @@ def get_xray_thin_d2cross_ei_integrated_thetae_dphi( ( E_e0_eV, E_ph_eV, theta_ph, nthetae, ndphi, + version, shape, verb, verb_tab, save, pfe_save, overwrite, - ) = _check( - # inputs - E_e0_eV=E_e0_eV, - E_ph_eV=E_ph_eV, - theta_ph=theta_ph, - # integration parameters - nthetae=nthetae, - ndphi=ndphi, - # verb - verb=verb, - verb_tab=verb_tab, - # save - save=save, - pfe_save=pfe_save, - overwrite=overwrite, - ) + ) = _check(**locals()) # ------------------ # compute @@ -188,6 +177,7 @@ def _check( # integration parameters nthetae=None, ndphi=None, + version=None, # verb verb=None, verb_tab=None, @@ -195,6 +185,8 @@ def _check( save=None, pfe_save=None, overwrite=None, + # unused + **kwdargs, ): # ----------- @@ -233,10 +225,6 @@ def _check( theta_ph=theta_ph, ) - E_e0_eV, E_ph_eV, theta_ph = np.broadcast_arrays( - E_e0_eV, E_ph_eV, theta_ph, - ) - # ----------- # integers # ----------- @@ -257,6 +245,22 @@ def _check( default=_NDPHI, ) + # ------------ + # version + # ------------ + + if version is None: + version = _VERSION + if isinstance(version, str): + version = [version] + + version = ds._generic_check._check_var_iter( + version, 'version', + types=(list, tuple), + types_iter=str, + allowed=['EH', 'BH', 'BHE'], + ) + # ----------- # verb # ----------- @@ -335,6 +339,7 @@ def _check( return ( E_e0_eV, E_ph_eV, theta_ph, nthetae, ndphi, + version, shape, verb, verb_tab, save, pfe_save, overwrite, @@ -363,6 +368,7 @@ def _compute( per_energy_unit=None, # misc shape=None, + daxis=None, verb=None, verb_tab=None, # unused @@ -387,6 +393,9 @@ def _compute( # loop on largest dimension iloop = np.argmax(shape) sli = np.array([slice(None)]*len(shape) + [None, None]) + sli_Ee0 = np.copy(sli) + sli_Ee1 = np.copy(sli) + sli_theta = np.copy(sli) slistr = [':'] * (len(shape) + 2) sli_ang = (None,) * (len(shape) - 1) + (slice(None),)*2 @@ -405,6 +414,10 @@ def _compute( kv = f"{kk}_{vv['units']}" if vv['units'] == 'eV' else kk d2cross[kk]['data'] = eval(kv) + # cross-sections + d2cross['cross'] = {vv: {'data': np.full(shape, 0.)} for vv in version} + srunits = asunits.Unit('sr') + # ------------------ # get d3cross # ------------------ @@ -421,7 +434,9 @@ def _compute( # sli sli[iloop] = ii - slit = tuple(sli) + sli_Ee0[iloop] = min(ii, E_e0_eV.shape[iloop]-1) + sli_Ee1[iloop] = min(ii, E_e1_eV.shape[iloop]-1) + sli_theta[iloop] = min(ii, theta_ph.shape[iloop]-1) # verb if verb >= 2: @@ -434,10 +449,10 @@ def _compute( d3cross = _xray_thin_target.get_xray_thin_d3cross_ei( # inputs Z=Z, - E_e0_eV=E_e0_eV[slit], - E_e1_eV=E_e1_eV[slit], + E_e0_eV=E_e0_eV[tuple(sli_Ee0)], + E_e1_eV=E_e1_eV[tuple(sli_Ee1)], # directions - theta_ph=theta_ph[slit], + theta_ph=theta_ph[tuple(sli_theta)], theta_e=theta_ef, dphi=dphif, # hypergeometric parameter @@ -451,20 +466,9 @@ def _compute( debug=False, ) - if ii == 0: - srunits = asunits.Unit('sr') - # cross-sections - d2cross['cross'] = { - vv: { - 'data': np.full(shape, 0.), - 'units': asunits.Unit(vcross['units']) * srunits, - } - for vv, vcross in d3cross['cross'].items() - } - # integrate of theta_e for vv, vcross in d3cross['cross'].items(): - d2cross['cross'][vv]['data'][slit[:-2]] = scpinteg.trapezoid( + d2cross['cross'][vv]['data'][tuple(sli[:-2])] = scpinteg.trapezoid( scpinteg.trapezoid( vcross['data'] * sinte, x=theta_e, @@ -473,6 +477,9 @@ def _compute( x=dphi, axis=-1, ) + d2cross['cross'][vv]['units'] = ( + srunits * asunits.Unit(vcross['units']) + ) return d2cross @@ -509,7 +516,7 @@ def _save( # extract boundaries path = os.path.abspath(_PATH_HERE) - fname = f"d2cross_Ee0{Ee0}_Eph{Eph}_ntheta{ntheta}.npz" + fname = f"d2cross_Ee0{Ee0}_Eph{Eph}_ntheta{ntheta}" pfe_save = os.path.join(path, f"{fname}.npz") # ---------- @@ -538,8 +545,8 @@ def _save( # verb # ---------- - if verb is True: - msg = "Saved d2cross in:\n\t{pfe_save}\n" + if verb >= 1: + msg = f"Saved d2cross in:\n\t{pfe_save}\n" print(msg) return @@ -565,17 +572,20 @@ def _format_vect2str(vect, base='eV'): # test scale # ------------- - dlog = np.diff(np.log(vect)) - dlin = np.diff(vect) - islog = np.allclose(dlog, dlog[0]) - islin = np.allclose(dlin, dlin[0]) + if np.max(vect.shape) == vect.size: + dlog = np.diff(np.log(vect)) + dlin = np.diff(vect) + islog = np.allclose(dlog, dlog[0]) + islin = np.allclose(dlin, dlin[0]) - if islog is True: - scale = 'log' - elif islin is True: - scale = 'lin' + if islog is True: + scale = 'log' + elif islin is True: + scale = 'lin' + else: + scale = 'pts' else: - scale = '' + scale = 'pts' # ------------- # format From da504ffbb0b3f1acf176dedc41f250fba4a1e643 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Fri, 9 Jan 2026 22:54:44 +0000 Subject: [PATCH 13/17] [#1166] Almost there loading d2cross --- .../emission/_xray_thin_target_integrated.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py index 4e5a54f7c..ff9fcd339 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated.py @@ -441,8 +441,10 @@ def _compute( # verb if verb >= 2: slistr[iloop] = str(ii) + str0 = ', '.join(slistr) + str1 = str(shape + (None, None)) end = '\n' if ii == size - 1 else '\r' - msg = f"\t{ii+1} / {size}, index ({', '.join(slistr)}) / {shape}" + msg = f"\t{ii+1} / {size}, index ({str0}) / {str1}" print(msg, end=end) # d3cross @@ -630,7 +632,10 @@ def _load( ) raise Exception(msg) - d2cross = dict(np.load(d2cross, allow_pickle=True)) + d2cross = { + kk: vv.tolist() + for kk, vv in np.load(d2cross, allow_pickle=True).items() + } # --------- # dict @@ -669,13 +674,14 @@ def _load( # ---------------- lok = ['BHE', 'BH', 'EH'] + typunits = (str, asunits.Unit, asunits.CompositeUnit) c0 = ( isinstance(d2cross.get('cross'), dict) and all([ kk in lok and isinstance(vv, dict) and isinstance(vv.get('data'), np.ndarray) - and isinstance(vv.get('data'), str) + and isinstance(vv.get('units'), typunits) for kk, vv in d2cross['cross'].items() ]) ) From ea1c4ddad02ac96f82100a67310c9194c499c84b Mon Sep 17 00:00:00 2001 From: dvezinet Date: Mon, 12 Jan 2026 16:00:26 +0000 Subject: [PATCH 14/17] [#1166] plot_xray_thin_d2cross_ei_anisotropy(d2cross=pfe, dcases=str) operational --- .../_xray_thin_target_integrated_cases.py | 72 ++++++ .../_xray_thin_target_integrated_plot.py | 211 ++++++++++-------- 2 files changed, 193 insertions(+), 90 deletions(-) create mode 100644 tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_cases.py diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_cases.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_cases.py new file mode 100644 index 000000000..fbbb1f285 --- /dev/null +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_cases.py @@ -0,0 +1,72 @@ + +# ANISOTROPY CASES +_DCASES_PRE = { + + # ------------- + # standard span + + 'standard': { + 0: { + 'E_e0_eV': 20e3, + 'E_ph_eV': 10e3, + 'color': 'r', + 'marker': '*', + 'ms': 14, + }, + 1: { + 'E_e0_eV': 100e3, + 'E_ph_eV': 50e3, + 'color': 'c', + 'marker': '*', + 'ms': 14, + }, + 2: { + 'E_e0_eV': 100e3, + 'E_ph_eV': 10e3, + 'color': 'm', + 'marker': '*', + 'ms': 14, + }, + 3: { + 'E_e0_eV': 1000e3, + 'E_ph_eV': 10e3, + 'color': (0.8, 0.8, 0), + 'marker': '*', + 'ms': 14, + }, + 4: { + 'E_e0_eV': 10000e3, + 'E_ph_eV': 10e3, + 'color': (0., 0.8, 0.8), + 'marker': '*', + 'ms': 14, + }, + 5: { + 'E_e0_eV': 1000e3, + 'E_ph_eV': 50e3, + 'color': (0.8, 0., 0.8), + 'marker': '*', + 'ms': 14, + }, + }, + + # ------------------ + # large span + + 'span_100eV_10MeV': { + 0: { + 'E_e0_eV': 20e3, + 'E_ph_eV': 10e3, + 'color': 'r', + 'marker': '*', + 'ms': 14, + }, + 1: { + 'E_e0_eV': 100e3, + 'E_ph_eV': 50e3, + 'color': 'c', + 'marker': '*', + 'ms': 14, + }, + }, +} diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py index 289485b0a..ed908aa22 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py @@ -14,6 +14,7 @@ from . import _xray_thin_target_integrated as _mod +from ._xray_thin_target_integrated_cases import _DCASES_PRE TupleDict = tuple[dict] @@ -34,59 +35,26 @@ # DSCALES _DSCALES = { - 'E_ph': 'linear', + 'E_ph': 'log', 'E_e0': 'log', 'theta': 'linear', } -# ANISOTROPY CASES -_DCASES = { - 0: { - 'E_e0_eV': 20e3, - 'E_ph_eV': 10e3, - 'color': 'r', - 'marker': '*', - 'ms': 14, - }, - 1: { - 'E_e0_eV': 100e3, - 'E_ph_eV': 50e3, - 'color': 'c', - 'marker': '*', - 'ms': 14, - }, - 2: { - 'E_e0_eV': 100e3, - 'E_ph_eV': 10e3, - 'color': 'm', - 'marker': '*', - 'ms': 14, - }, - 3: { - 'E_e0_eV': 1000e3, - 'E_ph_eV': 10e3, - 'color': (0.8, 0.8, 0), - 'marker': '*', - 'ms': 14, - }, - 4: { - 'E_e0_eV': 10000e3, - 'E_ph_eV': 10e3, - 'color': (0., 0.8, 0.8), - 'marker': '*', - 'ms': 14, - }, - 5: { - 'E_e0_eV': 1000e3, - 'E_ph_eV': 50e3, - 'color': (0.8, 0., 0.8), - 'marker': '*', - 'ms': 14, - }, +# ANISOTROPY CASES FORMATTING +_DCASES_FORMAT = { + 'E_e0_eV': (int, float), + 'E_ph_eV': (int, float), + 'color': (str, tuple), + 'marker': str, + 'ms': (int, float), } +# ANISOTROPY CASES DEFAULT +_DCASES_CASE = 'standard' + + # #################################################### # #################################################### # plot anisotropy @@ -146,7 +114,6 @@ def plot_xray_thin_d2cross_ei_anisotropy( ( E_e0_eV, E_ph_eV, theta_ph, version, - dcases, dscales, verb, fontsize, @@ -175,6 +142,22 @@ def plot_xray_thin_d2cross_ei_anisotropy( verb=verb, ) + # ------------------- + # update from d2cross + # ------------------- + + # if d2cross was provided + theta_ph = d2cross['theta_ph']['data'].ravel() + E_ph_eV = d2cross['E_ph']['data'].ravel() + E_e0_eV = d2cross['E_e0']['data'].ravel() + + # dcases + dcases = _check_dcases( + dcases=dcases, + E_e0_eV=E_e0_eV, + E_ph_eV=E_ph_eV, + ) + # -------------- # prepare axes # -------------- @@ -205,6 +188,10 @@ def plot_xray_thin_d2cross_ei_anisotropy( theta_ph*180/np.pi, axis=0, ) + mean_log10 = np.full(mean.shape, np.nan) + iok = np.isfinite(mean) + iok[iok] = mean[iok] > 0. + mean_log10[iok] = np.log10(mean[iok]) mean_units = vv['units'] # integral @@ -212,7 +199,7 @@ def plot_xray_thin_d2cross_ei_anisotropy( im0 = ax.contour( E_e0_eV * 1e-3, E_ph_eV * 1e-3, - np.log10(mean).T, + mean_log10.T, levels=dplot_mean['levels'], colors=dplot_mean['colors'], ) @@ -315,7 +302,8 @@ def plot_xray_thin_d2cross_ei_anisotropy( ) # limits - ax.set_ylim(0, ymax*1e-3) + if dscales['E_ph'] == 'linear': + ax.set_ylim(0, ymax*1e-3) # --------------- # plot - cases @@ -389,8 +377,6 @@ def _check_anisotropy( E_ph_eV=None, theta_ph=None, version=None, - # selected cases - dcases=None, # verb verb=None, # scales @@ -437,38 +423,6 @@ def _check_anisotropy( if version is None: version = _VERSION - # ------------ - # dcases - # ------------ - - ddef = copy.deepcopy(_DCASES) - if dcases in [None, True]: - dcases = ddef - - if dcases is not False: - for k0, v0 in dcases.items(): - dcases[k0] = _check_anisotropy_dplot( - v0, - f'dcases[{k0}]', - ddef[0], - ) - - # update with indices - ie = np.argmin(np.abs(E_e0_eV - dcases[k0]['E_e0_eV'])) - iph = np.argmin(np.abs(E_ph_eV - dcases[k0]['E_ph_eV'])) - dcases[k0].update({'ie': ie, 'iph': iph}) - - # update with label - ee0 = E_e0_eV[ie] - eph = E_ph_eV[iph] - dcases[k0]['lab'] = ( - r"$E_{e0} / E_{ph}$ = " - + f"{ee0*1e-3:3.0f} / {eph*1e-3:3.0f} keV = " - + f"{round(ee0 / eph, ndigits=1)}" - ) - else: - dcases = {} - # ----------- # verb # ----------- @@ -545,7 +499,6 @@ def _check_anisotropy( return ( E_e0_eV, E_ph_eV, theta_ph, version, - dcases, dscales, verb, fontsize, @@ -553,6 +506,69 @@ def _check_anisotropy( ) +def _check_dcases( + dcases=None, + E_e0_eV=None, + E_ph_eV=None, +): + + # -------------- + # dcases default + # -------------- + + ddef = copy.deepcopy(_DCASES_FORMAT) + if dcases in [None, True]: + dcases = _DCASES_CASE + + # -------------- + # dcases from predefined + # -------------- + + if isinstance(dcases, str): + lok = sorted(_DCASES_PRE.keys()) + if dcases not in lok: + lstr = [f"\t- {kk}" for kk in lok] + msg = ( + "Arg 'dcases' must be either:\n" + "\t- dict of cases\n" + "\t- a key to a predefined dict of cases\n" + "Available predefined dcases:\n" + + "\n".join(lstr) + ) + raise Exception(msg) + dcases = copy.deepcopy(_DCASES_PRE[dcases]) + + # -------------- + # generic check + # -------------- + + if dcases is not False: + for k0, v0 in dcases.items(): + dcases[k0] = _check_anisotropy_dplot( + v0, + f'dcases[{k0}]', + ddef, + ) + + # update with indices + ie = np.argmin(np.abs(E_e0_eV - dcases[k0]['E_e0_eV'])) + iph = np.argmin(np.abs(E_ph_eV - dcases[k0]['E_ph_eV'])) + dcases[k0].update({'ie': ie, 'iph': iph}) + + # update with label + ee0 = E_e0_eV[ie] + eph = E_ph_eV[iph] + dcases[k0]['lab'] = ( + r"$E_{e0} / E_{ph}$ = " + + f"{ee0*1e-3:3.0f} / {eph*1e-3:3.0f} keV = " + + f"{round(ee0 / eph, ndigits=1)}" + ) + else: + dcases = {} + + return dcases + + def _check_anisotropy_dplot(din, dname, ddef): # ------------- @@ -569,7 +585,11 @@ def _check_anisotropy_dplot(din, dname, ddef): if din is not False: c0 = ( isinstance(din, dict) - and all([kk in ddef.keys() for kk in din.keys()]) + and all([ + kk in ddef.keys() + and isinstance(din[kk], ddef[kk]) + for kk in din.keys() + ]) ) if not c0: lstr = [f"\t- ''{k0}': {v0}" for k0, v0 in ddef.items()] @@ -604,11 +624,18 @@ def _get_peaking(data, x, axis=None): # ---------- integ = scpinteg.trapezoid(data, x=x, axis=axis) - shape_integ = tuple([ - 1 if ii == axis else ss - for ii, ss in enumerate(data.shape) - ]) - data_n = data / integ.reshape(shape_integ) + shape_integ = list(data.shape) + shape_integ[axis] = 1 + + data_n = np.full(data.shape, np.nan) + iok = np.isfinite(integ) + iok[iok] = integ[iok] > 0 + iokn = iok.nonzero() + sli0 = list(iokn) + sli1 = list(iokn) + sli0.insert(axis, None) + sli1.insert(axis, slice(None)) + data_n[tuple(sli1)] = data[tuple(sli1)] / integ[tuple(sli0)] # ---------- # get average @@ -616,7 +643,11 @@ def _get_peaking(data, x, axis=None): shape_x = tuple([-1 if ii == axis else 1 for ii in range(data.ndim)]) xf = x.reshape(shape_x) - x_avf = scpinteg.simpson(data_n * xf, x=x, axis=axis).reshape(shape_integ) + x_avf = scpinteg.trapezoid( + data_n * xf, + x=x, + axis=axis, + ).reshape(shape_integ) std = np.sqrt(scpinteg.simpson(data_n * (xf - x_avf)**2, x=x, axis=axis)) return integ/180, 1/std From ae961214b0e5cefae1fa1f2f2b05bb09a68d1f31 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Mon, 12 Jan 2026 16:54:42 +0000 Subject: [PATCH 15/17] [#1166] plot_xray_thin_d2cross_ei_anisotropy() polished --- .../_xray_thin_target_integrated_cases.py | 58 +++++++++++++++---- .../_xray_thin_target_integrated_plot.py | 27 ++++++--- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_cases.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_cases.py index fbbb1f285..59cb7c78b 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_cases.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_cases.py @@ -53,20 +53,56 @@ # ------------------ # large span - 'span_100eV_10MeV': { + 'span_10eV_10MeV': { 0: { - 'E_e0_eV': 20e3, - 'E_ph_eV': 10e3, - 'color': 'r', - 'marker': '*', - 'ms': 14, + 'E_e0_eV': 100, + 'E_ph_eV': 50, + 'color': (1, 0, 0), }, 1: { - 'E_e0_eV': 100e3, - 'E_ph_eV': 50e3, - 'color': 'c', - 'marker': '*', - 'ms': 14, + 'E_e0_eV': 10e3, + 'E_ph_eV': 50, + 'color': (0.8, 0, 0), + }, + 2: { + 'E_e0_eV': 10e3, + 'E_ph_eV': 5e3, + 'color': (0, 1, 0), + }, + 3: { + 'E_e0_eV': 600e3, + 'E_ph_eV': 50, + 'color': (0.6, 0, 0), + }, + 4: { + 'E_e0_eV': 600e3, + 'E_ph_eV': 5e3, + 'color': (0, 0.75, 0), + }, + 5: { + 'E_e0_eV': 600e3, + 'E_ph_eV': 450e3, + 'color': (0, 0, 1), + }, + 6: { + 'E_e0_eV': 7e6, + 'E_ph_eV': 50, + 'color': (0.4, 0, 0), + }, + 7: { + 'E_e0_eV': 7e6, + 'E_ph_eV': 5e3, + 'color': (0, 0.5, 0), + }, + 8: { + 'E_e0_eV': 7e6, + 'E_ph_eV': 450e3, + 'color': (0, 0, 0.5), + }, + 9: { + 'E_e0_eV': 7e6, + 'E_ph_eV': 5.5e6, + 'color': 'y', }, }, } diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py index ed908aa22..a36ff11d4 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py @@ -43,11 +43,12 @@ # ANISOTROPY CASES FORMATTING _DCASES_FORMAT = { - 'E_e0_eV': (int, float), - 'E_ph_eV': (int, float), - 'color': (str, tuple), - 'marker': str, - 'ms': (int, float), + 'E_e0_eV': {'type': (int, float), 'val': 1e3}, + 'E_ph_eV': {'type': (int, float), 'val': 10e3}, + 'color': {'type': (str, tuple), 'val': 'k'}, + 'marker': {'type': str, 'val': '*'}, + 'ms': {'type': (int, float), 'val': 18}, + 'ls': {'type': str, 'val': '-'}, } @@ -326,6 +327,7 @@ def plot_xray_thin_d2cross_ei_anisotropy( theta_ph * 180/np.pi, yy / np.max(yy), c=vcase['color'], + ls=vcase['ls'], label=labi, ) @@ -338,6 +340,7 @@ def plot_xray_thin_d2cross_ei_anisotropy( theta_ph * 180/np.pi, yy*1e28, c=vcase['color'], + ls=vcase['ls'], label=labi, ) @@ -587,12 +590,16 @@ def _check_anisotropy_dplot(din, dname, ddef): isinstance(din, dict) and all([ kk in ddef.keys() - and isinstance(din[kk], ddef[kk]) + and ( + isinstance(ddef[kk], dict) + and ddef[kk].get('type') is not None + and isinstance(din[kk], ddef[kk]['type']) + ) for kk in din.keys() ]) ) if not c0: - lstr = [f"\t- ''{k0}': {v0}" for k0, v0 in ddef.items()] + lstr = [f"\t- '{k0}': {v0['type']}" for k0, v0 in ddef.items()] msg = ( f"Arg '{dname}' must be either False or a dict with:\n" + "\n".join(lstr) @@ -606,7 +613,11 @@ def _check_anisotropy_dplot(din, dname, ddef): if din is not False: for k0, v0 in ddef.items(): - din[k0] = din.get(k0, v0) + if isinstance(v0, dict): + vv = v0['val'] + else: + vv = v0 + din[k0] = din.get(k0, vv) return din From c5ac714b5a20c95c7f04531ca3fef70fea810dc5 Mon Sep 17 00:00:00 2001 From: dvezinet Date: Mon, 12 Jan 2026 21:06:43 +0000 Subject: [PATCH 16/17] [#1166] uniformized axes names + cascading dcases --- ...arget_integrated_dist_responsivity_plot.py | 85 ++++++++++++++----- .../_xray_thin_target_integrated_plot.py | 22 ++--- 2 files changed, 73 insertions(+), 34 deletions(-) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py index e50590e8d..01526215a 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py @@ -77,7 +77,8 @@ def plot_xray_thin_integ_dist_filter_anisotropy( # version version: Optional[str] = None, # selected cases - dcases: Optional[dict[int, dict]] = None, + dcases_cross: Optional[dict[int, dict]] = None, + dcases_dist_resp: Optional[dict[int, dict]] = None, # verb verb: Optional[bool] = None, # plot @@ -113,7 +114,7 @@ def plot_xray_thin_integ_dist_filter_anisotropy( # check inputs # --------------- - dcases, dscales, fs, fontsize = _check(**locals()) + dcases_dist_resp, dscales, fs, fontsize = _check(**locals()) # --------------- # prepare data @@ -155,7 +156,7 @@ def plot_xray_thin_integ_dist_filter_anisotropy( # version version=version, # selected cases - dcases=False, + dcases=dcases_cross, # verb verb=verb, # plot @@ -174,7 +175,7 @@ def plot_xray_thin_integ_dist_filter_anisotropy( if dax.get(kax) is not None: ax = dax[kax]['handle'] - for kk, vv in dcases.items(): + for kk, vv in dcases_dist_resp.items(): l0, = ax.plot( vv['responsivity']['data'], @@ -185,13 +186,14 @@ def plot_xray_thin_integ_dist_filter_anisotropy( lw=vv.get('lw', 1.), label=kk, ) - dcases[kk]['color'] = l0.get_color() + dcases_dist_resp[kk]['color'] = l0.get_color() ax.set_xlabel( vv['responsivity']['units'], fontsize=fontsize, fontweight='bold', ) + ax.invert_xaxis() # ------------------- # plot distribution @@ -201,7 +203,7 @@ def plot_xray_thin_integ_dist_filter_anisotropy( if dax.get(kax) is not None: ax = dax[kax]['handle'] - for kk, vv in dcases.items(): + for kk, vv in dcases_dist_resp.items(): ax.semilogy( vv['E_e0']['data']*1e-3, @@ -229,7 +231,7 @@ def plot_xray_thin_integ_dist_filter_anisotropy( def _check( - dcases=None, + dcases_dist_resp=None, fs=None, fontsize=None, E_e0_scale=None, @@ -246,11 +248,11 @@ def _check( # ------------ ddef = copy.deepcopy(_DCASES) - if dcases in [None, False]: - dcases = {} + if dcases_dist_resp in [None, False]: + dcases_dist_resp = {} else: - for k0, v0 in dcases.items(): - dcases[k0] = _check_case( + for k0, v0 in dcases_dist_resp.items(): + dcases_dist_resp[k0] = _check_case( v0, f"dcases['{k0}']", ddef[list(ddef.keys())[0]], @@ -294,9 +296,7 @@ def _check( default=_DSCALES[kk], ) - return ( - dcases, dscales, fs, fontsize, - ) + return dcases_dist_resp, dscales, fs, fontsize def _check_case( @@ -392,10 +392,10 @@ def _dax( fig = plt.figure(figsize=(15, 12)) fig.suptitle(tit, size=fontsize+2, fontweight='bold') - nh0, nh1, nh2 = 2, 5, 4 + nh0, nh1, nh2 = 2, 6, 4 nv0, nv1, nv2 = 3, 1, 2 gs = gridspec.GridSpec( - ncols=nh0+nh1+nh2 + 1, + ncols=nh0+nh1+2*(nh2 + 1), nrows=nv0 + nv1, **dmargin, ) @@ -465,20 +465,20 @@ def _dax( dax['dist'] = {'handle': ax, 'type': 'isolines'} # -------------- - # ax - theta - norm + # ax - theta_cross - norm ax = fig.add_subplot( - gs[:nv2, nh0 + nh1 + 1:], + gs[:nv2, nh0 + nh1 + 1:nh0 + nh1 + 1 + nh2], xscale=dscales['theta'], yscale='linear', ) ax.set_xlabel( - r"$\theta_{ph}^B$ (deg)", + r"$\theta_{ph}^{e0}$ (deg)", size=fontsize, fontweight='bold', ) ax.set_ylabel( - "emiss (ph/sr/s/m3)", + "cross-section norm.", size=fontsize, fontweight='bold', ) @@ -487,10 +487,49 @@ def _dax( dax['theta_norm'] = {'handle': ax, 'type': 'isolines'} # -------------- - # ax - theta - abs + # ax - theta_cross - abs + + ax = fig.add_subplot( + gs[nv2:, nh0 + nh1 + 1:nh0 + nh1 + 1 + nh2], + sharex=dax['theta_norm']['handle'], + yscale='log', + ) + ax.set_xlabel( + r"$\theta_{ph}^{e0}$ (deg)", + size=fontsize, + fontweight='bold', + ) + + # store + dax['theta_abs'] = {'handle': ax, 'type': 'isolines'} + + # -------------- + # ax - theta_emiss - norm + + ax = fig.add_subplot( + gs[:nv2, nh0 + nh1 + 2 + nh2:], + xscale=dscales['theta'], + yscale='linear', + ) + ax.set_xlabel( + r"$\theta_{ph}^B$ (deg)", + size=fontsize, + fontweight='bold', + ) + ax.set_ylabel( + "emiss norm.", + size=fontsize, + fontweight='bold', + ) + + # store + dax['theta_emiss_norm'] = {'handle': ax, 'type': 'isolines'} + + # -------------- + # ax - theta_emiss - abs ax = fig.add_subplot( - gs[nv2:, nh0 + nh1 + 1:], + gs[nv2:, nh0 + nh1 + 2 + nh2:], sharex=dax['theta_norm']['handle'], yscale='log', ) @@ -506,6 +545,6 @@ def _dax( ) # store - dax['theta_abs'] = {'handle': ax, 'type': 'isolines'} + dax['theta_emiss_abs'] = {'handle': ax, 'type': 'isolines'} return dax diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py index a36ff11d4..8688d968f 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_plot.py @@ -318,8 +318,8 @@ def plot_xray_thin_d2cross_ei_anisotropy( yy = vv['data'][:, vcase['ie'], vcase['iph']] if np.any(yy > 0): - # normalized - kax = 'norm' + # theta_norm + kax = 'theta_norm' if dax.get(kax) is not None: ax = dax[kax]['handle'] @@ -331,8 +331,8 @@ def plot_xray_thin_d2cross_ei_anisotropy( label=labi, ) - # abs - kax = 'log' + # theta_abs + kax = 'theta_abs' if dax.get(kax) is not None: ax = dax[kax]['handle'] @@ -345,7 +345,7 @@ def plot_xray_thin_d2cross_ei_anisotropy( ) # normalized - kax = 'norm' + kax = 'theta_norm' if dax.get(kax) is not None: ax = dax[kax]['handle'] ax.legend(prop={'size': 12}) @@ -353,7 +353,7 @@ def plot_xray_thin_d2cross_ei_anisotropy( ax.set_xlim(0, 180) # normalized - kax = 'log' + kax = 'theta_abs' if dax.get(kax) is not None: ax = dax[kax]['handle'] ax.legend(prop={'size': 12}) @@ -727,7 +727,7 @@ def _dax( dax['map'] = {'handle': ax, 'type': 'isolines'} # -------------- - # ax - norm + # ax - theta_norm ax = fig.add_subplot( gs[0, 1], @@ -750,14 +750,14 @@ def _dax( ) # store - dax['norm'] = {'handle': ax, 'type': 'isolines'} + dax['theta_norm'] = {'handle': ax, 'type': 'isolines'} # -------------- - # ax - log + # ax - theta_abs ax = fig.add_subplot( gs[1, 1], - sharex=dax['norm']['handle'], + sharex=dax['theta_norm']['handle'], ) ax.set_xlabel( r"$\theta_{ph}$ (deg)", @@ -771,6 +771,6 @@ def _dax( ) # store - dax['log'] = {'handle': ax, 'type': 'isolines'} + dax['theta_abs'] = {'handle': ax, 'type': 'isolines'} return dax From fd6e41550f958ebe063aa1e6a3603002622b167a Mon Sep 17 00:00:00 2001 From: dvezinet Date: Mon, 12 Jan 2026 22:09:58 +0000 Subject: [PATCH 17/17] [#1166] Started integrand --- ...arget_integrated_dist_responsivity_plot.py | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py index 01526215a..63eb89679 100644 --- a/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py +++ b/tofu/physics_tools/electrons/emission/_xray_thin_target_integrated_dist_responsivity_plot.py @@ -167,6 +167,15 @@ def plot_xray_thin_integ_dist_filter_anisotropy( dplot_mean=dplot_mean, ) + # ------------------- + # Compute integrand + # ------------------- + + dinteg = _integrand( + d2cross=d2cross, + dcases=dcases, + ) + # ------------------- # plot responsivity # ------------------- @@ -224,6 +233,28 @@ def plot_xray_thin_integ_dist_filter_anisotropy( return dax +# ############################################# +# ############################################# +# Integrand +# ############################################# + + +def _integrand( + d2cross=None, + dcases=None, +): + + # -------------- + # loop on cases + # -------------- + + for kcase, vcase in dcases.items(): + + pass + + return + + # ############################################# # ############################################# # Axes for anisotropy @@ -384,9 +415,9 @@ def _dax( ) dmargin = { - 'left': 0.06, 'right': 0.95, - 'bottom': 0.06, 'top': 0.90, - 'wspace': 0.20, 'hspace': 0.20, + 'left': 0.05, 'right': 0.98, + 'bottom': 0.05, 'top': 0.92, + 'wspace': 0.30, 'hspace': 0.20, } fig = plt.figure(figsize=(15, 12))