diff --git a/cpp/src/clients/python/srwlpy.cpp b/cpp/src/clients/python/srwlpy.cpp index b0c12214..3f965f35 100644 --- a/cpp/src/clients/python/srwlpy.cpp +++ b/cpp/src/clients/python/srwlpy.cpp @@ -24,6 +24,7 @@ #include "pyparse.h" //OC09032019 #include #include +#include #include //OCTEST_161214 //OC18022024 (commented-out) @@ -82,6 +83,7 @@ static const char strEr_BadOptZP[] = "Incorrect Optical Zone Plate structure"; static const char strEr_BadOptWG[] = "Incorrect Optical Waveguide structure"; static const char strEr_BadOptG[] = "Incorrect Optical Grating structure"; static const char strEr_BadOptT[] = "Incorrect Optical Generic Transmission structure"; +static const char strEr_BadOptR[] = "Incorrect Reflectivity Object structure"; static const char strEr_BadOptMir[] = "Incorrect Optical Mirror structure"; static const char strEr_BadOptCryst[] = "Incorrect Optical Crystal structure"; static const char strEr_BadListIntProp[] = "Incorrect list structure defining intensity distributions to be plotted after propagation"; @@ -2019,6 +2021,96 @@ void ParseSructSRWLOptT(SRWLOptT* pOpt, PyObject* oOpt, vector* pvBuf } } +// NW11072025 +/************************************************************************** + * Parses PyObject* to SRWLOptR* + * **************************************************************************/ +void ParseSructSRWLOptR(SRWLOptR* pOpt, PyObject* oOpt, vector* pvBuf) //throw(...) +{ + if((pOpt == 0) || (oOpt == 0)) throw strEr_NoObj; + + PyObject *o_tmp = 0; + Py_ssize_t sizeRefl = 0; + o_tmp = PyObject_GetAttrString(oOpt, "arRefl"); + if(o_tmp == 0) throw strEr_BadOptR; + pOpt->arRefl = (double*)GetPyArrayBuf(o_tmp, pvBuf, &sizeRefl); + if(pOpt->arRefl == 0) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + Py_DECREF(o_tmp); + + o_tmp = PyObject_GetAttrString(oOpt, "reflNumPhEn"); + if(o_tmp == 0) throw strEr_BadOptR; + if(!PyNumber_Check(o_tmp)) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + pOpt->reflNumPhEn = PyLong_AsLong(o_tmp); + if(PyErr_Occurred()) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + Py_DECREF(o_tmp); + + o_tmp = PyObject_GetAttrString(oOpt, "reflNumAng"); + if(o_tmp == 0) throw strEr_BadOptR; + if(!PyNumber_Check(o_tmp)) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + pOpt->reflNumAng = PyLong_AsLong(o_tmp); + if(PyErr_Occurred()) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + Py_DECREF(o_tmp); + + o_tmp = PyObject_GetAttrString(oOpt, "reflNumComp"); + if(o_tmp == 0) throw strEr_BadOptR; + if(!PyNumber_Check(o_tmp)) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + pOpt->reflNumComp = PyLong_AsLong(o_tmp); + if(PyErr_Occurred()) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + Py_DECREF(o_tmp); + + o_tmp = PyObject_GetAttrString(oOpt, "reflPhEnScaleType"); + if(o_tmp == 0) throw strEr_BadOptR; + if(!PyUnicode_Check(o_tmp) && !PyBytes_Check(o_tmp)) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + CopyPyStringToC(o_tmp, pOpt->reflPhEnScaleType, 3); + Py_DECREF(o_tmp); + + o_tmp = PyObject_GetAttrString(oOpt, "reflAngScaleType"); + if(o_tmp == 0) throw strEr_BadOptR; + if(!PyUnicode_Check(o_tmp) && !PyBytes_Check(o_tmp)) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + CopyPyStringToC(o_tmp, pOpt->reflAngScaleType, 3); + Py_DECREF(o_tmp); + + o_tmp = PyObject_GetAttrString(oOpt, "reflPhEnStart"); + if(o_tmp == 0) throw strEr_BadOptR; + if(!PyNumber_Check(o_tmp)) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + pOpt->reflPhEnStart = PyFloat_AsDouble(o_tmp); + if(PyErr_Occurred()) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + Py_DECREF(o_tmp); + + o_tmp = PyObject_GetAttrString(oOpt, "reflPhEnFin"); + if(o_tmp == 0) throw strEr_BadOptR; + if(!PyNumber_Check(o_tmp)) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + pOpt->reflPhEnFin = PyFloat_AsDouble(o_tmp); + if(PyErr_Occurred()) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + Py_DECREF(o_tmp); + + o_tmp = PyObject_GetAttrString(oOpt, "reflAngStart"); + if(o_tmp == 0) throw strEr_BadOptR; + if(!PyNumber_Check(o_tmp)) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + pOpt->reflAngStart = PyFloat_AsDouble(o_tmp); + if(PyErr_Occurred()) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + Py_DECREF(o_tmp); + + o_tmp = PyObject_GetAttrString(oOpt, "reflAngFin"); + if(o_tmp == 0) throw strEr_BadOptR; + if(!PyNumber_Check(o_tmp)) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + pOpt->reflAngFin = PyFloat_AsDouble(o_tmp); + if(PyErr_Occurred()) { Py_DECREF(o_tmp); throw strEr_BadOptR; } + Py_DECREF(o_tmp); + + if((pOpt->reflNumPhEn <= 0) || (pOpt->reflNumAng <= 0) || + (pOpt->reflNumComp < 1) || (pOpt->reflNumComp > 2)) throw strEr_BadOptR; + + size_t nVal = (size_t)pOpt->reflNumPhEn; + size_t nAng = (size_t)pOpt->reflNumAng; + size_t nComp = (size_t)pOpt->reflNumComp; + size_t maxSize = numeric_limits::max(); + if((nVal > maxSize/nAng) || ((nVal *= nAng) > maxSize/nComp) || + ((nVal *= nComp) > maxSize/2) || ((nVal *= 2) > maxSize/sizeof(double))) throw strEr_BadOptR; + size_t sizeReflExp = nVal*sizeof(double); + if((sizeRefl < 0) || ((size_t)sizeRefl != sizeReflExp)) throw strEr_BadOptR; +} + /************************************************************************//** * Parses PyObject* to SRWLOptMir* ***************************************************************************/ @@ -2026,6 +2118,10 @@ void ParseSructSRWLOptMir(SRWLOptMir* pOpt, PyObject* oOpt, vector* p { if((pOpt == 0) || (oOpt == 0)) throw strEr_NoObj; + pOpt->arReflObj = 0; + pOpt->nReflObj = 0; + pOpt->arReflDist = 0; + //PyObject *o_tmp = PyObject_GetAttrString(oOpt, "arRefl"); //pOpt->arRefl = (double*)GetPyArrayBuf(o_tmp, pvBuf, 0); //if(pOpt->arRefl == 0) throw strEr_BadOptMir; @@ -2228,6 +2324,55 @@ void ParseSructSRWLOptMir(SRWLOptMir* pOpt, PyObject* oOpt, vector* p pOpt->isConvex = (char)PyLong_AsLong(o_tmp); Py_DECREF(o_tmp); } + + //NW11072025 ------------------------------ + o_tmp = PyObject_GetAttrString(oOpt, "arReflObj"); + if(o_tmp == 0) + { + PyErr_Clear(); + return; + } + if(o_tmp == Py_None) + { + Py_DECREF(o_tmp); + return; + } + if(!PyList_Check(o_tmp)) { Py_DECREF(o_tmp); throw strEr_BadOptMir; } + + int nReflObj = (int)PyList_Size(o_tmp); + if(nReflObj <= 0) { Py_DECREF(o_tmp); throw strEr_BadOptMir; } + vector vReflObj(nReflObj); + for(int i=0; inpt <= 0) || (pOpt->nps <= 0)) { Py_DECREF(o_tmp); throw strEr_BadOptMir; } + + size_t nMap = (size_t)pOpt->npt; + size_t nps = (size_t)pOpt->nps; + size_t maxSize = numeric_limits::max(); + if((nMap > maxSize/nps) || ((nMap *= nps) > maxSize/sizeof(int))) { Py_DECREF(o_tmp); throw strEr_BadOptMir; } + size_t sizeReflDistExp = nMap*sizeof(int); + if((sizeReflDist < 0) || ((size_t)sizeReflDist != sizeReflDistExp)) { Py_DECREF(o_tmp); throw strEr_BadOptMir; } + for(size_t i=0; i= nReflObj)) { Py_DECREF(o_tmp); throw strEr_BadOptMir; } + } + Py_DECREF(o_tmp); + + pOpt->arReflObj = new SRWLOptR[nReflObj]; + for(int i=0; iarReflObj[i] = vReflObj[i]; + pOpt->nReflObj = nReflObj; + pOpt->arReflDist = pReflDist; + //NW11072025 ------------------------------ } /************************************************************************//** @@ -2381,6 +2526,32 @@ void ParseSructSRWLOptMirExtHyp(SRWLOptMirHyp* pOpt, PyObject* oOpt) //throw(... Py_DECREF(o_tmp); } +/************************************************************************//** + * Deallocates a parsed mirror of a specific type. + ***************************************************************************/ +template void DeallocSRWLOptMirType(void* pMir) +{ + T *pMirTyped = (T*)pMir; + if(pMirTyped->baseMir.arReflObj != 0) delete[] pMirTyped->baseMir.arReflObj; + delete pMirTyped; +} + +/************************************************************************//** + * Deallocates a parsed mirror and its reflectivity object array. + ***************************************************************************/ +void DeallocSRWLOptMir(void* pMir, const char* sType) +{ + if((pMir == 0) || (sType == 0)) return; + + if(strcmp(sType, "mirror: plane") == 0) DeallocSRWLOptMirType(pMir); + else if(strcmp(sType, "mirror: ellipsoid") == 0) DeallocSRWLOptMirType(pMir); + else if(strcmp(sType, "mirror: paraboloid") == 0) DeallocSRWLOptMirType(pMir); + else if(strcmp(sType, "mirror: toroid") == 0) DeallocSRWLOptMirType(pMir); + else if(strcmp(sType, "mirror: sphere") == 0) DeallocSRWLOptMirType(pMir); + else if(strcmp(sType, "mirror: hyperboloid") == 0) DeallocSRWLOptMirType(pMir); +} + + /************************************************************************//** * Parses PyObject* to a SRWLOptMir* ***************************************************************************/ @@ -2401,46 +2572,54 @@ void* ParseSructSRWLOptMirAll(PyObject* oOpt, char* sPyTypeName, vectorbaseMir), oOpt, pvBuf); - } - else if(strcmp(sPyTypeName, "SRWLOptMirEl") == 0) - { - pMir = new SRWLOptMirEl(); - strcat(srwOptTypeName, "ellipsoid\0"); - ParseSructSRWLOptMir(&(((SRWLOptMirEl*)pMir)->baseMir), oOpt, pvBuf); - ParseSructSRWLOptMirExtEl((SRWLOptMirEl*)pMir, oOpt); - } - else if(strcmp(sPyTypeName, "SRWLOptMirPar") == 0) - { - pMir = new SRWLOptMirPar(); - strcat(srwOptTypeName, "paraboloid\0"); - ParseSructSRWLOptMir(&(((SRWLOptMirPar*)pMir)->baseMir), oOpt, pvBuf); - ParseSructSRWLOptMirExtPar((SRWLOptMirPar*)pMir, oOpt); - } - else if(strcmp(sPyTypeName, "SRWLOptMirTor") == 0) - { - pMir = new SRWLOptMirTor(); - strcat(srwOptTypeName, "toroid\0"); - ParseSructSRWLOptMir(&(((SRWLOptMirTor*)pMir)->baseMir), oOpt, pvBuf); - ParseSructSRWLOptMirExtTor((SRWLOptMirTor*)pMir, oOpt); - } - else if (strcmp(sPyTypeName, "SRWLOptMirSph") == 0) + try { - pMir = new SRWLOptMirSph(); - strcat(srwOptTypeName, "sphere\0"); - ParseSructSRWLOptMir(&(((SRWLOptMirSph*)pMir)->baseMir), oOpt, pvBuf); - ParseSructSRWLOptMirExtSph((SRWLOptMirSph*)pMir, oOpt); + if(strcmp(sPyTypeName, "SRWLOptMirPl") == 0) + { + pMir = new SRWLOptMirPl(); + strcat(srwOptTypeName, "plane\0"); + ParseSructSRWLOptMir(&(((SRWLOptMirPl*)pMir)->baseMir), oOpt, pvBuf); + } + else if(strcmp(sPyTypeName, "SRWLOptMirEl") == 0) + { + pMir = new SRWLOptMirEl(); + strcat(srwOptTypeName, "ellipsoid\0"); + ParseSructSRWLOptMir(&(((SRWLOptMirEl*)pMir)->baseMir), oOpt, pvBuf); + ParseSructSRWLOptMirExtEl((SRWLOptMirEl*)pMir, oOpt); + } + else if(strcmp(sPyTypeName, "SRWLOptMirPar") == 0) + { + pMir = new SRWLOptMirPar(); + strcat(srwOptTypeName, "paraboloid\0"); + ParseSructSRWLOptMir(&(((SRWLOptMirPar*)pMir)->baseMir), oOpt, pvBuf); + ParseSructSRWLOptMirExtPar((SRWLOptMirPar*)pMir, oOpt); + } + else if(strcmp(sPyTypeName, "SRWLOptMirTor") == 0) + { + pMir = new SRWLOptMirTor(); + strcat(srwOptTypeName, "toroid\0"); + ParseSructSRWLOptMir(&(((SRWLOptMirTor*)pMir)->baseMir), oOpt, pvBuf); + ParseSructSRWLOptMirExtTor((SRWLOptMirTor*)pMir, oOpt); + } + else if (strcmp(sPyTypeName, "SRWLOptMirSph") == 0) + { + pMir = new SRWLOptMirSph(); + strcat(srwOptTypeName, "sphere\0"); + ParseSructSRWLOptMir(&(((SRWLOptMirSph*)pMir)->baseMir), oOpt, pvBuf); + ParseSructSRWLOptMirExtSph((SRWLOptMirSph*)pMir, oOpt); + } + else if(strcmp(sPyTypeName, "SRWLOptMirHyp") == 0) //TW24012024 + { + pMir = new SRWLOptMirHyp(); + strcat(srwOptTypeName, "hyperboloid\0"); + ParseSructSRWLOptMir(&(((SRWLOptMirHyp*)pMir)->baseMir), oOpt, pvBuf); + ParseSructSRWLOptMirExtHyp((SRWLOptMirHyp*)pMir, oOpt); + } } - else if(strcmp(sPyTypeName, "SRWLOptMirHyp") == 0) //TW24012024 + catch(...) { - pMir = new SRWLOptMirHyp(); - strcat(srwOptTypeName, "hyperboloid\0"); - ParseSructSRWLOptMir(&(((SRWLOptMirHyp*)pMir)->baseMir), oOpt, pvBuf); - ParseSructSRWLOptMirExtHyp((SRWLOptMirHyp*)pMir, oOpt); + DeallocSRWLOptMir(pMir, srwOptTypeName); + throw; } return pMir; @@ -2452,13 +2631,10 @@ void* ParseSructSRWLOptMirAll(PyObject* oOpt, char* sPyTypeName, vector* pvBuf) //throw(...) { if((pOpt == 0) || (oOpt == 0)) throw strEr_NoObj; + pOpt->mirSub = 0; + pOpt->mirSubType[0] = '\0'; PyObject *o_tmp = 0; - o_tmp = PyObject_GetAttrString(oOpt, "mirSub"); - if(o_tmp == 0) throw strEr_BadOptG; - pOpt->mirSub = ParseSructSRWLOptMirAll(o_tmp, 0, pvBuf, pOpt->mirSubType); - Py_DECREF(o_tmp); - o_tmp = PyObject_GetAttrString(oOpt, "m"); if(o_tmp == 0) throw strEr_BadOptG; if(!PyNumber_Check(o_tmp)) throw strEr_BadOptG; @@ -2525,6 +2701,11 @@ void ParseSructSRWLOptG(SRWLOptG* pOpt, PyObject* oOpt, vector* pvBuf Py_DECREF(o_tmp); } } + + o_tmp = PyObject_GetAttrString(oOpt, "mirSub"); + if(o_tmp == 0) throw strEr_BadOptG; + pOpt->mirSub = ParseSructSRWLOptMirAll(o_tmp, 0, pvBuf, pOpt->mirSubType); + Py_DECREF(o_tmp); } /************************************************************************//** @@ -4204,7 +4385,13 @@ void DeallocOptCntArrays(SRWLOptC* pOptCnt) else if(strcmp(sType, "lens") == 0) delete (SRWLOptL*)(pOptCnt->arOpt[i]); else if(strcmp(sType, "zp") == 0) delete (SRWLOptZP*)(pOptCnt->arOpt[i]); else if(strcmp(sType, "waveguide") == 0) delete (SRWLOptWG*)(pOptCnt->arOpt[i]); - else if(strcmp(sType, "grating") == 0) delete (SRWLOptG*)(pOptCnt->arOpt[i]); + else if(strcmp(sType, "grating") == 0) + { + SRWLOptG *pG = (SRWLOptG*)(pOptCnt->arOpt[i]); + DeallocSRWLOptMir(pG->mirSub, pG->mirSubType); + delete pG; + } + else if(strncmp(sType, "mirror: ", 8) == 0) DeallocSRWLOptMir(pOptCnt->arOpt[i], sType); else if(strcmp(sType, "transmission") == 0) delete (SRWLOptT*)(pOptCnt->arOpt[i]); //{ // SRWLOptT *pT = (SRWLOptT*)(pOptCnt->arOpt[i]); diff --git a/env/python/requirements-optional.txt b/env/python/requirements-optional.txt index 3e01382d..ea211af2 100644 --- a/env/python/requirements-optional.txt +++ b/env/python/requirements-optional.txt @@ -3,4 +3,5 @@ oasys_srw primme pykern mpld3 -scikit-image \ No newline at end of file +scikit-image +xraydb diff --git a/env/python/srwpy/examples/SRWLIB_Example22.py b/env/python/srwpy/examples/SRWLIB_Example22.py new file mode 100644 index 00000000..4fc9f62e --- /dev/null +++ b/env/python/srwpy/examples/SRWLIB_Example22.py @@ -0,0 +1,239 @@ +############################################################################# +# SRWLIB Example # 22: Material-dependent reflective and refractive optics +# Simulating a Gaussian X-ray beam passing through an Aluminum filter, +# a B4C mirror, a 20 nm Pt-coated Si mirror, and an array of diamond CRLs +# v 0.01 +############################################################################# + +from __future__ import print_function + +try: #OC15112022 + import sys + sys.path.append('../') + from srwlib import * + from uti_plot import * + from uti_mtrl import * +except: + from srwpy.srwlib import * + from srwpy.uti_plot import * + from srwpy.uti_mtrl import * + +import os +from copy import deepcopy + + +print('SRWLIB Python Example # 22:') +print('Simulating material-dependent reflective and refractive X-ray optics') + + +#******************* Photon Energy and Material Parameters +photon_energy = 8000. # [eV] + +n_ph_en = 101 +n_ang = 401 +n_comp = 2 +ph_en_start = 7500. +ph_en_fin = 8500. +ang_start = 0.001 +ang_fin = 0.005 +ang_graz = 0.003 + +# Aluminum filter +filter_thick = 50.e-06 # [m] +al_delta, al_atten_len = calc_delta_atten_len('Al', photon_energy) +opFilter = srwl_opt_setup_transm_from_material( + 'Al', filter_thick, photon_energy, + _rx=2.e-03, _ry=2.e-03, _nx=2, _ny=2, _ext_tr=1 +) + +# B4C mirror; B4C is a formula not registered with a default xraydb density +b4c_density = 2.5 # [g/cm^3] +b4c_refl = calc_refl_arr( + 'B4C', _dens=b4c_density, + _n_ph_en=n_ph_en, _n_ang=n_ang, _n_comp=n_comp, + _ph_en_start=ph_en_start, _ph_en_fin=ph_en_fin, + _ang_start=ang_start, _ang_fin=ang_fin, +) + +# 20 nm Pt coating on a Si substrate; xraydb expects thickness in Angstroms +pt_coating_thick = 200. # [Angstrom] +pt_si_refl = calc_coated_refl_arr( + 'Pt', pt_coating_thick, 'Si', + _n_ph_en=n_ph_en, _n_ang=n_ang, _n_comp=n_comp, + _ph_en_start=ph_en_start, _ph_en_fin=ph_en_fin, + _ang_start=ang_start, _ang_fin=ang_fin, +) + +# Diamond CRL material: elemental C at diamond density +diamond_density = 3.52 # [g/cm^3] +diamond_delta, diamond_atten_len = calc_delta_atten_len( + 'C', photon_energy, _dens=diamond_density +) + +print('Al filter: delta = {:.6g}, attenuation length = {:.6g} m'.format( + al_delta, al_atten_len +)) +print('Diamond: delta = {:.6g}, attenuation length = {:.6g} m'.format( + diamond_delta, diamond_atten_len +)) + + +#********************** Output Files +strDataFolderName = 'data_example_22' +strIntInFileName = 'ex22_res_int_in.dat' +strIntOutFileName = 'ex22_res_int_out.dat' + +if not os.path.exists(strDataFolderName): + os.makedirs(strDataFolderName) + + +#********************** Gaussian Beam Source +gsnBm = SRWLGsnBm() +gsnBm.x = 0 +gsnBm.y = 0 +gsnBm.z = 0 +gsnBm.xp = 0 +gsnBm.yp = 0 +gsnBm.avgPhotEn = photon_energy +gsnBm.pulseEn = 0.001 +gsnBm.repRate = 1 +gsnBm.polar = 1 # linear horizontal polarization +gsnBm.sigX = 20.e-06 +gsnBm.sigY = 20.e-06 +gsnBm.sigT = 10.e-15 +gsnBm.mx = 0 +gsnBm.my = 0 + +wfr = SRWLWfr() +wfr.allocate(1, 151, 151) +wfr.mesh.zStart = 10. +wfr.mesh.eStart = photon_energy +wfr.mesh.eFin = photon_energy +wfr.mesh.xStart = -0.3e-03 +wfr.mesh.xFin = 0.3e-03 +wfr.mesh.yStart = -0.3e-03 +wfr.mesh.yFin = 0.3e-03 +wfr.unitElFld = 2 +wfr.partBeam.partStatMom1.x = gsnBm.x +wfr.partBeam.partStatMom1.y = gsnBm.y +wfr.partBeam.partStatMom1.z = gsnBm.z +wfr.partBeam.partStatMom1.xp = gsnBm.xp +wfr.partBeam.partStatMom1.yp = gsnBm.yp + +srwl.CalcElecFieldGaussian(wfr, gsnBm, [2]) + +meshIn = deepcopy(wfr.mesh) +arIIn = array('f', [0]*meshIn.nx*meshIn.ny) +srwl.CalcIntFromElecField(arIIn, wfr, 6, 0, 3, meshIn.eStart, 0, 0) +srwl_uti_save_intens_ascii( + arIIn, meshIn, os.path.join(strDataFolderName, strIntInFileName), 0 +) + + +#********************** Optical Elements +# Uniform Aluminum filter. +filter_amp = opFilter.arTr[0] + +mirror_len = 0.2 +mirror_width = 5.e-03 + +# B4C plane mirror, deflecting vertically +opMirB4C = SRWLOptMirPl( + _size_tang=mirror_len, _size_sag=mirror_width, + _nvx=0, _nvy=cos(ang_graz), _nvz=-sin(ang_graz), + _tvx=0, _tvy=-sin(ang_graz), + _refl=b4c_refl, + _n_ph_en=n_ph_en, _n_ang=n_ang, _n_comp=n_comp, + _ph_en_start=ph_en_start, _ph_en_fin=ph_en_fin, + _ang_start=ang_start, _ang_fin=ang_fin, +) + +# Pt-coated Si plane mirror, deflecting horizontally +opMirPtSi = SRWLOptMirPl( + _size_tang=mirror_len, _size_sag=mirror_width, + _nvx=cos(ang_graz), _nvy=0, _nvz=-sin(ang_graz), + _tvx=-sin(ang_graz), _tvy=0, + _refl=pt_si_refl, + _n_ph_en=n_ph_en, _n_ang=n_ang, _n_comp=n_comp, + _ph_en_start=ph_en_start, _ph_en_fin=ph_en_fin, + _ang_start=ang_start, _ang_fin=ang_fin, +) + +# Array of ten 2D parabolic diamond CRLs +crl_apert = 1.e-03 +crl_r_min = 100.e-06 +crl_number = 10 +crl_wall_thick = 20.e-06 +if diamond_delta > 0: + opCRL = srwl_opt_setup_CRL( + 3, diamond_delta, diamond_atten_len, 1, + crl_apert, crl_apert, crl_r_min, crl_number, crl_wall_thick, + 0, 0, None, 0, 0, 201, 201 + ) + crl_focal_len = opCRL.Fx +else: + print('Warning: diamond refractive properties are unavailable; the CRL is disabled.') + opCRL = SRWLOptT( + _nx=2, _ny=2, _rx=crl_apert, _ry=crl_apert, + _extTr=1, _alloc_base=[1, 0] + ) + crl_focal_len = 1. + +opDrFilter_M1 = SRWLOptD(1.) +opDrM1_M2 = SRWLOptD(1.) +opDrM2_CRL = SRWLOptD(1.) +opDrCRL_Obs = SRWLOptD(crl_focal_len) + + +#********************** Propagation +ppOpt = [0, 0, 1., 1, 0, 1., 1., 1., 1., 0, 0, 0] +ppDrift = [0, 0, 1., 1, 0, 1., 1., 1., 1., 0, 0, 0] +ppCRL = [0, 0, 1., 1, 0, 1.1, 2., 1.1, 2., 0, 0, 0] +ppFinal = [0, 0, 1., 1, 0, 0.2, 2., 0.2, 2., 0, 0, 0] + +optBL = SRWLOptC( + [opFilter, opDrFilter_M1, opMirB4C, opDrM1_M2, + opMirPtSi, opDrM2_CRL, opCRL, opDrCRL_Obs], + [ppOpt, ppDrift, ppOpt, ppDrift, ppOpt, ppDrift, ppCRL, ppFinal] +) + +print(' Propagating wavefront through filter, mirrors, and diamond CRLs ... ', end='') +srwl.PropagElecField(wfr, optBL) +print('done') + +meshOut = deepcopy(wfr.mesh) +arIOut = array('f', [0]*meshOut.nx*meshOut.ny) +srwl.CalcIntFromElecField(arIOut, wfr, 6, 0, 3, meshOut.eStart, 0, 0) +srwl_uti_save_intens_ascii( + arIOut, meshOut, os.path.join(strDataFolderName, strIntOutFileName), 0 +) + + +def integrated_intensity(arI, mesh): + dx = (mesh.xFin - mesh.xStart)/(mesh.nx - 1) if mesh.nx > 1 else 1. + dy = (mesh.yFin - mesh.yStart)/(mesh.ny - 1) if mesh.ny > 1 else 1. + return sum(arI)*dx*dy + + +intIn = integrated_intensity(arIIn, meshIn) +intOut = integrated_intensity(arIOut, meshOut) +print('CRL focal length: {:.6g} m'.format(crl_focal_len)) +print('Al filter intensity transmission: {:.6g}'.format(filter_amp*filter_amp)) +print('Integrated beamline transmission: {:.6g}'.format(intOut/intIn)) + + +#********************** Plotting +uti_plot2d1d( + arIIn, + [1.e+03*meshIn.xStart, 1.e+03*meshIn.xFin, meshIn.nx], + [1.e+03*meshIn.yStart, 1.e+03*meshIn.yFin, meshIn.ny], + labels=['Horizontal Position [mm]', 'Vertical Position [mm]', 'Input Intensity'] +) +uti_plot2d1d( + arIOut, + [1.e+06*meshOut.xStart, 1.e+06*meshOut.xFin, meshOut.nx], + [1.e+06*meshOut.yStart, 1.e+06*meshOut.yFin, meshOut.ny], + labels=['Horizontal Position [microns]', 'Vertical Position [microns]', 'Focused Intensity'] +) +uti_plot_show() +print('done') diff --git a/env/python/srwpy/examples/SRWLIB_ExamplesRunAll.py b/env/python/srwpy/examples/SRWLIB_ExamplesRunAll.py index 9774106f..200bdc55 100644 --- a/env/python/srwpy/examples/SRWLIB_ExamplesRunAll.py +++ b/env/python/srwpy/examples/SRWLIB_ExamplesRunAll.py @@ -32,6 +32,7 @@ 'SRWLIB_Example18.py', 'SRWLIB_Example19.py', 'SRWLIB_Example21.py', + 'SRWLIB_Example22.py', ] for i in range(len(exFileNames)): diff --git a/env/python/srwpy/uti_mtrl.py b/env/python/srwpy/uti_mtrl.py new file mode 100644 index 00000000..ce92a306 --- /dev/null +++ b/env/python/srwpy/uti_mtrl.py @@ -0,0 +1,590 @@ +"""Material Utilities Module + + +Modules: + + calc_refl_arr + calc_coated_refl_arr + calc_multilayer_refl_arr + calc_delta_atten_len + srwl_opt_setup_transm_from_material + add_mat + + +.. moduleauthor:: Nathan Whittington +.. moduleauthor:: Roman Chernikov +""" +# updated 22-06-2026 + +from __future__ import print_function # Python 2.7 compatibility + +import inspect +from array import array + +import numpy as np + +try: + from srwlib import SRWLOptT +except Exception: + from .srwlib import SRWLOptT + + +xraydb = None +xraydb_found = False +xraydb_version = '' + +try: + import xraydb + xraydb_found = True + xraydb_version = getattr(xraydb, '__version__', '') +except Exception as exc: + print("Warning: 'xraydb' can not be loaded: {}. Reflectivity is set to 1 and refractive optical elements use vacuum properties.".format(exc)) + + +def _xraydb_function(_name): + if not xraydb_found: + return None + return getattr(xraydb, _name, None) + + +mirror_reflectivity = _xraydb_function('mirror_reflectivity') +coated_reflectivity = _xraydb_function('coated_reflectivity') +multilayer_reflectivity = _xraydb_function('multilayer_reflectivity') +xray_delta_beta = _xraydb_function('xray_delta_beta') +get_material = _xraydb_function('get_material') +add_material = _xraydb_function('add_material') + + +def _warn_refl_fallback(_message): + print("Warning: {} Reflectivity is set to 1.".format(_message)) + return 1 + + +def _check_xraydb_function(_func, _func_name): + if not xraydb_found: + return _warn_refl_fallback("'xraydb' is not available.") + if _func is None: + version_text = " version {}".format(xraydb_version) if xraydb_version else '' + return _warn_refl_fallback( + "'{}' is not available in xraydb{}.".format(_func_name, version_text) + ) + try: + if 'output' not in inspect.signature(_func).parameters: + version_text = " version {}".format(xraydb_version) if xraydb_version else '' + return _warn_refl_fallback( + "'{}' in xraydb{} does not support complex amplitude output.".format( + _func_name, version_text + ) + ) + except (TypeError, ValueError): + pass + return None + + +def _check_refl_parameters(_n_ph_en, _n_ang, _n_comp, _ph_en_start, _ph_en_fin, + _ph_en_scale_type, _ang_start, _ang_fin, _ang_scale_type): + try: + n_ph_en = int(_n_ph_en) + n_ang = int(_n_ang) + n_comp = int(_n_comp) + except (TypeError, ValueError): + return _warn_refl_fallback( + "Numbers of photon energy, grazing angle, and component points must be integers." + ) + if n_ph_en < 2: + return _warn_refl_fallback( + "At least two photon energy points are required by SRW reflectivity interpolation." + ) + if n_ang < 2: + return _warn_refl_fallback( + "At least two grazing angle points are required by SRW reflectivity interpolation." + ) + if n_comp not in (1, 2): + return _warn_refl_fallback("Number of reflectivity components must be 1 or 2.") + if _ph_en_scale_type not in ('lin', 'log'): + return _warn_refl_fallback( + "Invalid photon energy scale type '{}'; use 'lin' or 'log'.".format(_ph_en_scale_type) + ) + if _ang_scale_type not in ('lin', 'log'): + return _warn_refl_fallback( + "Invalid grazing angle scale type '{}'; use 'lin' or 'log'.".format(_ang_scale_type) + ) + if (_ph_en_scale_type == 'log') or (_ang_scale_type == 'log'): + return _warn_refl_fallback( + "Logarithmic sampling is not supported by SRW reflectivity interpolation." + ) + try: + if (_ph_en_start <= 0) or (_ph_en_fin <= 0): + return _warn_refl_fallback("Photon energy limits must be positive.") + if (_ang_start < 0) or (_ang_fin < 0): + return _warn_refl_fallback("Grazing angle limits can not be negative.") + if _ph_en_start == _ph_en_fin: + return _warn_refl_fallback("Photon energy limits must define a non-zero range.") + if _ang_start == _ang_fin: + return _warn_refl_fallback("Grazing angle limits must define a non-zero range.") + except TypeError: + return _warn_refl_fallback("Photon energy and grazing angle limits must be numbers.") + return None + + +def _sampling(_start, _fin, _n, _scale_type): + if _scale_type == 'lin': + return np.linspace(_start, _fin, _n) + print("Warning: logarithmic scale is not supported by SRW reflectivity interpolation yet.") + return np.logspace(np.log10(_start), np.log10(_fin), _n) + + +def _material_is_defined(_material, _density): + if _density is not None: + return True + if (get_material is None) or (_material is None): + return False + try: + return get_material(_material) is not None + except Exception: + return False + + +def _check_material(_material, _density, _description='material'): + if _material_is_defined(_material, _density): + return None + return _warn_refl_fallback( + "Density of {} '{}' was not found; specify the density or register the " + "material with add_mat().".format(_description, _material) + ) + + +def _refractive_fallback(_ph_en, _message): + print("Warning: {} Refractive index decrement is set to 0 and attenuation is disabled.".format(_message)) + try: + energy = np.asarray(_ph_en, dtype=float) + except (TypeError, ValueError): + return 0.0, 1.e+23 + if energy.ndim == 0: + return 0.0, 1.e+23 + return array('d', [0.0]*energy.size), array('d', [1.e+23]*energy.size) + + +def _resolve_material(_material, _density): + if _density is not None: + try: + density = float(_density) + except (TypeError, ValueError): + return None + if (not np.isfinite(density)) or (density <= 0): + return None + return _material, density + if get_material is None: + return None + try: + return get_material(_material) + except Exception: + return None + + +def _refl_to_srw_array(_refl_s, _refl_p, _n_tot): + try: + refl = np.ravel(_refl_s).view(np.float64) + if _refl_p is not None: + refl = np.concatenate((refl, np.ravel(_refl_p).view(np.float64))) + except (TypeError, ValueError) as exc: + return _warn_refl_fallback( + "Calculated reflectivity can not be converted to an SRW array: {}.".format(exc) + ) + if _n_tot != len(refl): + return _warn_refl_fallback( + "Calculated reflectivity array length {} does not match expected length {}.".format( + len(refl), _n_tot + ) + ) + if not np.all(np.isfinite(refl)): + return _warn_refl_fallback("Calculated reflectivity contains invalid values.") + return array('d', refl) + + +def calc_refl_arr( + _mat, + _n_ph_en=1, + _n_ang=1, + _n_comp=1, + _ph_en_start=0, + _ph_en_fin=0, + _ph_en_scale_type='lin', + _ang_start=0, + _ang_fin=0, + _ang_scale_type='lin', + _dens=None, + _roughness=0.0 +): + """Calculate complex mirror reflectivity vs photon energy and grazing angle. + + The result is a C-aligned flat array ordered by polarization, grazing angle, + photon energy, and real / imaginary component. If xraydb or required material + data are unavailable, a warning is printed and perfect reflectivity is used. + """ + fallback = _check_xraydb_function(mirror_reflectivity, 'mirror_reflectivity') + if fallback is not None: + return fallback + fallback = _check_refl_parameters( + _n_ph_en, _n_ang, _n_comp, _ph_en_start, _ph_en_fin, + _ph_en_scale_type, _ang_start, _ang_fin, _ang_scale_type + ) + if fallback is not None: + return fallback + fallback = _check_material(_mat, _dens) + if fallback is not None: + return fallback + + n_ph_en = int(_n_ph_en) + n_ang = int(_n_ang) + n_comp = int(_n_comp) + ph_en = _sampling(_ph_en_start, _ph_en_fin, n_ph_en, _ph_en_scale_type) + ang = _sampling(_ang_start, _ang_fin, n_ang, _ang_scale_type) + + try: + refl_s = mirror_reflectivity( + _mat, ang, ph_en, density=_dens, roughness=_roughness, + polarization='s', output='amplitude' + ) + refl_p = None + if n_comp == 2: + refl_p = mirror_reflectivity( + _mat, ang, ph_en, density=_dens, roughness=_roughness, + polarization='p', output='amplitude' + ) + except Exception as exc: + return _warn_refl_fallback( + "xraydb mirror reflectivity calculation for '{}' failed: {}.".format(_mat, exc) + ) + + return _refl_to_srw_array(refl_s, refl_p, n_ph_en*n_ang*n_comp*2) + + +def calc_coated_refl_arr( + _coating, + _coating_thick, + _substr, + _n_ph_en=1, + _n_ang=1, + _n_comp=1, + _ph_en_start=0, + _ph_en_fin=0, + _ph_en_scale_type='lin', + _ang_start=0, + _ang_fin=0, + _ang_scale_type='lin', + _coating_dens=None, + _substr_dens=None, + _binder=None, + _binder_dens=None, + _binder_thick=0.0, + _surf_roughness=0.0, + _substr_roughness=0.0, +): + """Calculate complex reflectivity of a coated mirror.""" + fallback = _check_xraydb_function(coated_reflectivity, 'coated_reflectivity') + if fallback is not None: + return fallback + fallback = _check_refl_parameters( + _n_ph_en, _n_ang, _n_comp, _ph_en_start, _ph_en_fin, + _ph_en_scale_type, _ang_start, _ang_fin, _ang_scale_type + ) + if fallback is not None: + return fallback + if _coating_thick is None: + return _warn_refl_fallback("Coating thickness is not specified.") + fallback = _check_material(_coating, _coating_dens, 'coating material') + if fallback is not None: + return fallback + fallback = _check_material(_substr, _substr_dens, 'substrate material') + if fallback is not None: + return fallback + if _binder is not None: + fallback = _check_material(_binder, _binder_dens, 'binder material') + if fallback is not None: + return fallback + + n_ph_en = int(_n_ph_en) + n_ang = int(_n_ang) + n_comp = int(_n_comp) + ph_en = _sampling(_ph_en_start, _ph_en_fin, n_ph_en, _ph_en_scale_type) + ang = _sampling(_ang_start, _ang_fin, n_ang, _ang_scale_type) + kwargs = dict( + coating_dens=_coating_dens, + surface_roughness=_surf_roughness, + substrate_dens=_substr_dens, + substrate_roughness=_substr_roughness, + binder=_binder, + binder_thick=_binder_thick, + binder_dens=_binder_dens, + output='amplitude', + ) + + try: + refl_s = coated_reflectivity( + _coating, _coating_thick, _substr, ang, ph_en, + polarization='s', **kwargs + ) + refl_p = None + if n_comp == 2: + refl_p = coated_reflectivity( + _coating, _coating_thick, _substr, ang, ph_en, + polarization='p', **kwargs + ) + except Exception as exc: + return _warn_refl_fallback( + "xraydb coated reflectivity calculation failed: {}.".format(exc) + ) + + return _refl_to_srw_array(refl_s, refl_p, n_ph_en*n_ang*n_comp*2) + + +def calc_multilayer_refl_arr( + _stackup, + _thickness, + _substr, + _n_periods, + _n_ph_en=1, + _n_ang=1, + _n_comp=1, + _ph_en_start=0, + _ph_en_fin=0, + _ph_en_scale_type='lin', + _ang_start=0, + _ang_fin=0, + _ang_scale_type='lin', + _dens=None, + _substr_dens=None, + _substr_rough=0, + _surf_rough=0, +): + """Calculate complex reflectivity of a periodic multilayer mirror.""" + fallback = _check_xraydb_function(multilayer_reflectivity, 'multilayer_reflectivity') + if fallback is not None: + return fallback + fallback = _check_refl_parameters( + _n_ph_en, _n_ang, _n_comp, _ph_en_start, _ph_en_fin, + _ph_en_scale_type, _ang_start, _ang_fin, _ang_scale_type + ) + if fallback is not None: + return fallback + if (_stackup is None) or (_thickness is None): + return _warn_refl_fallback("Multilayer materials and thicknesses must be specified.") + try: + n_layers = len(_stackup) + if n_layers != len(_thickness): + return _warn_refl_fallback( + "Number of multilayer materials does not match number of thicknesses." + ) + except TypeError: + return _warn_refl_fallback("Multilayer materials and thicknesses must be sequences.") + try: + n_periods = int(_n_periods) + except (TypeError, ValueError): + return _warn_refl_fallback("Number of multilayer periods must be an integer.") + if n_periods < 1: + return _warn_refl_fallback("Number of multilayer periods must be positive.") + if _dens is not None: + try: + if len(_dens) != n_layers: + return _warn_refl_fallback( + "Number of multilayer densities does not match number of materials." + ) + except TypeError: + return _warn_refl_fallback("Multilayer densities must be a sequence.") + + densities = [None]*len(_stackup) if _dens is None else _dens + for material, density in zip(_stackup, densities): + fallback = _check_material(material, density, 'multilayer material') + if fallback is not None: + return fallback + fallback = _check_material(_substr, _substr_dens, 'substrate material') + if fallback is not None: + return fallback + + n_ph_en = int(_n_ph_en) + n_ang = int(_n_ang) + n_comp = int(_n_comp) + ph_en = _sampling(_ph_en_start, _ph_en_fin, n_ph_en, _ph_en_scale_type) + ang = _sampling(_ang_start, _ang_fin, n_ang, _ang_scale_type) + kwargs = dict( + n_periods=n_periods, + density=_dens, + substrate_density=_substr_dens, + substrate_rough=_substr_rough, + surface_rough=_surf_rough, + output='amplitude', + ) + + try: + refl_s = multilayer_reflectivity( + _stackup, _thickness, _substr, ang, ph_en, + polarization='s', **kwargs + ) + refl_p = None + if n_comp == 2: + refl_p = multilayer_reflectivity( + _stackup, _thickness, _substr, ang, ph_en, + polarization='p', **kwargs + ) + except Exception as exc: + return _warn_refl_fallback( + "xraydb multilayer reflectivity calculation failed: {}.".format(exc) + ) + + return _refl_to_srw_array(refl_s, refl_p, n_ph_en*n_ang*n_comp*2) + + +def calc_delta_atten_len(_mat, _ph_en, _dens=None): + """Calculate material properties used by SRW refractive optical elements. + + :param _mat: material name or chemical formula + :param _ph_en: photon energy [eV], or a sequence of photon energies + :param _dens: material density [g/cm^3]; if omitted, use the xraydb material database + :return: refractive index decrement and attenuation length [m] + + Scalar photon energy returns two floats. A sequence returns two ``array('d')`` + objects suitable for spectrally-dependent SRW transmission elements and CRLs. + """ + if (not xraydb_found) or (xray_delta_beta is None): + return _refractive_fallback(_ph_en, "'xraydb.xray_delta_beta' is unavailable.") + + try: + energy = np.asarray(_ph_en, dtype=float) + except (TypeError, ValueError): + return _refractive_fallback(_ph_en, "Photon energy must be numeric.") + if (energy.size < 1) or (not np.all(np.isfinite(energy))) or np.any(energy <= 0): + return _refractive_fallback(_ph_en, "Photon energy values must be finite and positive.") + + material = _resolve_material(_mat, _dens) + if material is None: + return _refractive_fallback( + _ph_en, + "Density of material '{}' was not found; specify _dens or register the material with add_mat().".format(_mat) + ) + formula, density = material + + try: + energy_arg = float(energy) if energy.ndim == 0 else energy + delta, _beta, atten_len_cm = xray_delta_beta(formula, density, energy_arg) + delta = np.asarray(delta, dtype=float) + atten_len_m = 0.01*np.asarray(atten_len_cm, dtype=float) + except Exception as exc: + return _refractive_fallback( + _ph_en, "xraydb refractive-property calculation for '{}' failed: {}.".format(_mat, exc) + ) + + if (not np.all(np.isfinite(delta))) or (not np.all(np.isfinite(atten_len_m))) or np.any(atten_len_m <= 0): + return _refractive_fallback( + _ph_en, "Calculated refractive properties for '{}' contain invalid values.".format(_mat) + ) + + if energy.ndim == 0: + return float(delta), float(atten_len_m) + return array('d', delta.ravel()), array('d', atten_len_m.ravel()) + + +def srwl_opt_setup_transm_from_material( + _mat, + _thick, + _ph_en, + _dens=None, + _rx=1.e-03, + _ry=1.e-03, + _nx=2, + _ny=2, + _x=0, + _y=0, + _ext_tr=1, + _fx=1.e+23, + _fy=1.e+23 +): + """Set up a uniform transmission element from material data. + + :param _mat: material name or chemical formula + :param _thick: material thickness [m] + :param _ph_en: photon energy [eV], or a sequence of photon energies + :param _dens: material density [g/cm^3]; if omitted, use the xraydb material database + :param _rx: horizontal coordinate range [m] + :param _ry: vertical coordinate range [m] + :param _nx: number of points vs horizontal position + :param _ny: number of points vs vertical position + :param _x: horizontal transverse coordinate of center [m] + :param _y: vertical transverse coordinate of center [m] + :param _ext_tr: transmission outside the grid/mesh is zero (0), or same as boundary (1) + :param _fx: estimated focal length in the horizontal plane [m] + :param _fy: estimated focal length in the vertical plane [m] + :return: SRWLOptT by default + """ + delta, atten_len = calc_delta_atten_len(_mat, _ph_en, _dens) + + try: + thick = float(_thick) + if (not np.isfinite(thick)) or (thick < 0): + raise ValueError + except (TypeError, ValueError): + print("Warning: material thickness is invalid. Transmission element is set to vacuum.") + thick = 0.0 + + try: + energy = np.asarray(_ph_en, dtype=float).ravel() + if energy.size < 1: + raise ValueError + except (TypeError, ValueError): + print("Warning: photon energy is invalid. Transmission element mesh energy is set to 0.") + energy = np.asarray([0.0]) + + ne = int(energy.size) + try: + nx = int(_nx) + ny = int(_ny) + if (nx <= 0) or (ny <= 0): + raise ValueError + except (TypeError, ValueError): + print("Warning: transmission mesh dimensions are invalid. A 2 x 2 mesh is used.") + nx = 2 + ny = 2 + + delta_arr = np.asarray(delta, dtype=float).ravel() + atten_len_arr = np.asarray(atten_len, dtype=float).ravel() + if (delta_arr.size != ne) or (atten_len_arr.size != ne): + if (delta_arr.size == 1) and (atten_len_arr.size == 1): + delta_arr = np.full(ne, delta_arr[0]) + atten_len_arr = np.full(ne, atten_len_arr[0]) + else: + print("Warning: material data size is inconsistent with photon energy mesh. Transmission element is set to vacuum.") + delta_arr = np.zeros(ne) + atten_len_arr = np.full(ne, 1.e+23) + + if (not np.all(np.isfinite(delta_arr))) or (not np.all(np.isfinite(atten_len_arr))) or np.any(atten_len_arr <= 0): + print("Warning: material data contain invalid values. Transmission element is set to vacuum.") + delta_arr = np.zeros(ne) + atten_len_arr = np.full(ne, 1.e+23) + + amp = np.exp(-0.5*thick/atten_len_arr) + opd = -delta_arr*thick + ar_tr_one_point = np.empty(2*ne, dtype=float) + ar_tr_one_point[0::2] = amp + ar_tr_one_point[1::2] = opd + ar_tr = array('d', np.tile(ar_tr_one_point, nx*ny)) + + op_t = SRWLOptT( + _nx=nx, _ny=ny, _rx=_rx, _ry=_ry, _arTr=ar_tr, _extTr=_ext_tr, + _Fx=_fx, _Fy=_fy, _x=_x, _y=_y, _ne=ne, + _eStart=float(energy[0]), _eFin=float(energy[-1]) + ) + + return op_t + +def add_mat(name, formula, density, categories=None): + """Add a material to the user-local xraydb material database.""" + if add_material is None: + print("Warning: 'xraydb.add_material' is not available. Material was not added.") + return + if density is None: + print("Warning: density of '{}' is not specified. Material was not added.".format(formula)) + return + try: + add_material(name, formula, density, categories) + except Exception as exc: + print("Warning: material '{}' was not added: {}".format(name, exc))