├── lightweaver ├── DefaultIterSchemes │ └── .placeholder ├── Data │ ├── pf_Kurucz.input │ ├── Quadratures.pickle │ ├── AtomicMassesNames.pickle │ ├── AbundancesAsplund09.pickle │ ├── DefaultMolecules │ │ ├── SiO.molecule │ │ ├── H2+.molecule │ │ ├── H2O.molecule │ │ ├── NH.molecule │ │ ├── NO.molecule │ │ ├── O2.molecule │ │ ├── C2.molecule │ │ ├── LiH.molecule │ │ ├── N2.molecule │ │ ├── OH.molecule │ │ ├── CaH.molecule │ │ ├── H2.molecule │ │ ├── CO_NLTE.molecule │ │ ├── HF.molecule │ │ ├── CN.molecule │ │ ├── CO.molecule │ │ ├── TiO.molecule │ │ ├── CH.molecule │ │ └── MgH.molecule │ ├── Barklem_pddata.dat │ ├── Barklem_dfdata.dat │ └── Barklem_spdata.dat ├── constants.py ├── simd_management.py ├── __init__.py ├── config.py ├── multi.py ├── benchmark.py ├── nr_update.py ├── iteration_update.py ├── barklem.py └── zeeman.py ├── Utils ├── J_A+A_636_A24 │ ├── il3n1.dat │ ├── il7n3.dat │ ├── iql3n3.dat │ ├── iql5n4.dat │ ├── il9n6.dat │ ├── il11n7.dat │ ├── iql7n7.dat │ ├── iql9n8.dat │ ├── il13n10.dat │ ├── il15n11.dat │ ├── iql11n11.dat │ ├── iql13n14.dat │ ├── iql15n18.dat │ └── ReadMe ├── MultiDQuadratures.py └── GenerateDispatcher.py ├── examples ├── README.rst ├── plot_JudgeDynamicValidation.py └── plot_SimpleLineTest.py ├── MANIFEST.in ├── Source ├── LuSolve.hpp ├── WinCompile.bat ├── CmoArrayHelper.pxd ├── WindowsExtensionStub.cpp ├── Background.hpp ├── LightweaverAmalgamated.cpp ├── LwIterationResult.hpp ├── CmoArrayHelper.pyx ├── LwFormalInterfacePosix.hpp ├── TaskSetWrapper.hpp ├── LwContext.hpp ├── Dispatch_compute_full_operator_rates.ipp ├── Simd.hpp ├── LwFormalInterfaceWin.hpp ├── LwExtraParams.hpp ├── FastBackground.hpp ├── Atmosphere.cpp ├── Utils.hpp ├── Faddeeva.hh ├── Dispatch_chi_eta_aux_accum.ipp ├── Dispatch_intensity_core_opt.ipp ├── LwMisc.hpp ├── FormalInterface.cpp ├── Lightweaver.hpp ├── LuSolve.cpp ├── Constants.hpp ├── .clang-format ├── Bezier.hpp ├── LwInternal.hpp ├── LwAtom.hpp ├── LwFormalInterface.hpp ├── LwTransition.hpp ├── LwAtmosphere.hpp ├── Ng.hpp ├── ThreadStorage.hpp └── FastBackground.cpp ├── docs ├── Makefile ├── make.bat ├── conf.py ├── lightweaver-api.rst └── index.rst ├── M1Notes.md ├── pyproject.toml ├── LICENSE ├── .github └── workflows │ ├── build.yml │ ├── build_deploy.yml │ └── build_docs.yml └── README.md /lightweaver/DefaultIterSchemes/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/il3n1.dat: -------------------------------------------------------------------------------- 1 | 0.125 54.73561031724534 45.0 2 | -------------------------------------------------------------------------------- /lightweaver/Data/pf_Kurucz.input: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goobley/Lightweaver/HEAD/lightweaver/Data/pf_Kurucz.input -------------------------------------------------------------------------------- /lightweaver/Data/Quadratures.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goobley/Lightweaver/HEAD/lightweaver/Data/Quadratures.pickle -------------------------------------------------------------------------------- /examples/README.rst: -------------------------------------------------------------------------------- 1 | Lightweaver Examples Gallery 2 | ============================ 3 | 4 | See some simple examples for using Lightweaver. -------------------------------------------------------------------------------- /lightweaver/Data/AtomicMassesNames.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goobley/Lightweaver/HEAD/lightweaver/Data/AtomicMassesNames.pickle -------------------------------------------------------------------------------- /lightweaver/Data/AbundancesAsplund09.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Goobley/Lightweaver/HEAD/lightweaver/Data/AbundancesAsplund09.pickle -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | include environment.yml 4 | recursive-include lightweaver/Data * 5 | include lightweaver/DefaultIterSchemes/.placeholder 6 | -------------------------------------------------------------------------------- /Source/LuSolve.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LUSOLVE_HPP 2 | #define CMO_LUSOLVE_HPP 3 | 4 | #include "CmoArray.hpp" 5 | 6 | void solve_lin_eq(F64View2D A, F64View b, bool improve=true); 7 | 8 | #endif -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/il7n3.dat: -------------------------------------------------------------------------------- 1 | 0.036628086288183176 72.815257123995777988 59.351062157784859608 2 | 0.043001543341446451 30.403535361681147009 59.351062157784838291 3 | 0.045370370370370373 67.792345701403519342 14.351062157784845397 4 | -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/iql3n3.dat: -------------------------------------------------------------------------------- 1 | 0.031886790427685094 82.158077873553594372 33.471203989955910174 2 | 0.041029876238981577 30.186715179429441491 33.471203989955924385 3 | 0.052083333333333336 63.434948822922009981 78.471203989955924385 4 | -------------------------------------------------------------------------------- /Source/WinCompile.bat: -------------------------------------------------------------------------------- 1 | cl /GS /GL /W3 /Gy /Zc:wchar_t /Z7 /Gm- /O2 /sdl- /Zc:inline /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /arch:AVX2 /Gd /MD /std:c++17 /FC /EHsc /nologo /diagnostics:column /DEBUG:FULL BasicLwDriver.cpp 2 | -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/iql5n4.dat: -------------------------------------------------------------------------------- 1 | 0.030088353834933051 76.524052220579449113 60.768398523195656935 2 | 0.042736793710787514 69.126779158459726204 15.768398523195653382 3 | 0.025001917218612939 24.668834456356183438 15.768398523195646277 4 | 0.027172935235666495 44.210946052277940055 60.768398523195642724 5 | -------------------------------------------------------------------------------- /Source/CmoArrayHelper.pxd: -------------------------------------------------------------------------------- 1 | from CmoArray cimport F64View, F64View2D, F64View3D, F64View4D, f64 2 | cdef F64View f64_view(f64[::1] memview) except + 3 | cdef F64View2D f64_view_2(f64[:, ::1] memview) except + 4 | cdef F64View3D f64_view_3(f64[:, :, ::1] memview) except + 5 | cdef F64View4D f64_view_4(f64[:, :, :, ::1] memview) except + -------------------------------------------------------------------------------- /Utils/MultiDQuadratures.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pickle 3 | 4 | folder = 'J_A+A_636_A24/' 5 | basenames = ['il3n1', 'il7n3', 'il9n6', 'il11n7', 'il13n10', 'il15n11'] 6 | files = [folder + f for f in [f + '.dat' for f in basenames]] 7 | 8 | quads = {k: np.genfromtxt(f) for k, f in zip(basenames, files)} 9 | 10 | with open('Quadratures.pickle', 'wb') as pkl: 11 | pickle.dump(quads, pkl) -------------------------------------------------------------------------------- /Source/WindowsExtensionStub.cpp: -------------------------------------------------------------------------------- 1 | #include "JasPP.hpp" 2 | 3 | #ifndef LW_MODULE_STUB_NAME 4 | #error "This file must be included with the value of LW_MODULE_STUB_NAME defined to be the module name (for the Windows linker)" 5 | #else 6 | 7 | extern "C" 8 | { 9 | #include "Python.h" 10 | 11 | PyMODINIT_FUNC JasConcat(PyInit_, LW_MODULE_STUB_NAME)() 12 | { 13 | return nullptr; 14 | } 15 | 16 | } 17 | #endif -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/il9n6.dat: -------------------------------------------------------------------------------- 1 | 0.022713607613686692 42.888919507001126874 25.472196549014533673 2 | 0.022787199039712287 73.843778267985157981 9.414172189274722768 3 | 0.022110192033907447 74.483965373691674472 40.115473591126260544 4 | 0.022654670920532104 47.784764086172664577 69.270041431671216969 5 | 0.018155933946312444 77.017686215932542382 69.815112660867995942 6 | 0.016578396445849030 18.072787166919894730 83.128833713327637156 7 | -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/il11n7.dat: -------------------------------------------------------------------------------- 1 | 0.017454557861781708 44.411657683931743179 19.993151343707815215 2 | 0.012270710186718273 83.011005759864204379 36.663089772808156397 3 | 0.018536963106887672 77.141111058229668629 67.772767782906782941 4 | 0.019678953313802889 20.354714180837010673 48.566748820829324984 5 | 0.019637910647233123 47.075622439624495996 72.772886951905007891 6 | 0.015657498014902245 61.017667986267582592 42.023533434905061767 7 | 0.021763406868674069 74.378602548558319540 7.641868857030893025 8 | -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/iql7n7.dat: -------------------------------------------------------------------------------- 1 | 0.015847015063176075 18.536674369025785580 10.097500772838976957 2 | 0.013757295125958881 50.442642991916891049 81.362256684836808063 3 | 0.019342447264483847 76.061480718062441042 78.291595821595379334 4 | 0.018575002294609639 37.566767655461589470 53.528236689458495334 5 | 0.013380933807854508 85.413251505351723836 18.200571355255053874 6 | 0.020443758335841621 54.337987292630756997 19.977786880792884006 7 | 0.023653548108075434 73.031421317845257590 48.493782741689386739 8 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/SiO.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | SiO 3 | 4 | # Charge 5 | 0 6 | 7 | # Constituent elements 8 | SI, O 9 | 10 | # Ediss [eV] 11 | 8.26 12 | 13 | # Fit type for partion function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | 19 | # pf_coef 20 | 5 4.2275 -1.9144 0.7201 -1.3099 1.1657 21 | 22 | # eqc_coef 23 | 5 11.8772 -0.8349 -0.5248 1.1271 -1.1984 24 | 25 | # Filenames with line data 26 | ## end 27 | -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/iql9n8.dat: -------------------------------------------------------------------------------- 1 | 0.019289356035900717 76.343606272093822440 72.917320563644238973 2 | 0.015114566322221879 48.211017955310055072 49.029067306007945604 3 | 0.019684767944196598 76.048819919714972571 42.938520459367211402 4 | 0.010675866835903073 22.101169427031475578 82.597642282498753730 5 | 0.010800083023191791 20.350857184816486978 37.591110940423632769 6 | 0.014678112530955844 49.244655176876257485 78.651200998706158884 7 | 0.019464988039525417 76.229225861115693874 12.937243176409179313 8 | 0.015292259268104687 48.485392239551316607 18.723196561309801922 9 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/H2+.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | H2+ 3 | 4 | # Charge 5 | 1 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | 2H 9 | 10 | # Ediss [eV] 11 | -10.948 12 | 13 | # Fit type for partion function and equilibrium constant 14 | KURUCZ_70 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | 19 | # pf_coef 20 | 0 21 | 22 | # eqc_coef 23 | 5 -9.2120E+00 1.9386E-03 -4.7676E-07 7.4004E-11 -4.5518E-15 24 | 25 | # Filenames with line data 26 | 27 | ## end 28 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/H2O.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | H2O 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | 2H, O 9 | 10 | # Ediss [eV] 11 | 9.512 12 | 13 | # Fit type for partition function and equilibrium constant 14 | KURUCZ_70 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 6.0E+3 18 | 19 | # pf_coef 20 | 0 21 | 22 | # eqc_coef 23 | 5 -9.3179E+01 2.6725E-03 -5.7830E-07 8.5268E-11 -5.1311E-15 24 | 25 | # Filenames with line data 26 | 27 | ## end 28 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/NH.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | NH 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | N, H 9 | 10 | # Ediss [eV] 11 | 3.470 12 | 13 | # Fit type for partion function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | 19 | # pf_coef 20 | 4 3.0735 -1.8501 0.9607 -0.3935 21 | 22 | # eqc_coef 23 | 3 10.4609 -0.7387 -0.5713 24 | 25 | # Filenames with line data 26 | 27 | ## end 28 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/NO.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | NO 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | N, O 9 | 10 | # Ediss [eV] 11 | 6.4968 12 | 13 | # Fit type for partion function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | 19 | # pf_coef 20 | 3 4.3073 -1.8255 0.3765 21 | 22 | # eqc_coef 23 | 4 11.2253 -0.8143 0.0446 -0.4529 24 | 25 | # Filenames with line data 26 | 27 | ## end 28 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/O2.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | O2 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | 2O 9 | 10 | # Ediss [eV] 11 | 5.1156 12 | 13 | # Fit type for partition function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | 19 | # pf_coef 20 | 4 4.0636 -2.0779 0.7660 -0.2111 21 | 22 | # eqc_coef 23 | 3 11.8558 -0.5309 -0.6214 24 | 25 | # Filenames with line data 26 | 27 | ## end 28 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/C2.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | C2 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | 2C 9 | 10 | # Ediss [eV] 11 | 6.210 12 | 13 | # Fit type for partition function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 16.0E+3 18 | 19 | # pf_coef 20 | 4 4.3091 -2.2406 0.4865 -0.2049 21 | 22 | # eqc_coef 23 | 3 11.4576 -0.4458 -0.1818 24 | 25 | ## ../../Molecules/C2/c2da0600_12.kur 26 | ## end 27 | 28 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/LiH.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | LIH 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | Li, H 9 | 10 | # Ediss [eV] 11 | 2.4287 12 | 13 | # Fit type for partion function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | 19 | # pf_coef 20 | 4 3.2564 -2.1799 0.7236 -0.1052 21 | 22 | # eqc_coef 23 | 5 9.9506 -0.5163 0.1833 -1.7211 1.2805 24 | 25 | # Filenames with line data 26 | 27 | ## end 28 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/N2.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | N2 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | 2N 9 | 10 | # Ediss [eV] 11 | 9.7594 12 | 13 | # Fit type for partition function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | 19 | # pf_coef 20 | 3 3.2643 -1.7303 0.4192 21 | 22 | # eqc_coef 23 | 5 11.8838 -0.8915 0.2929 -1.4220 0.9007 24 | 25 | # Filenames with line data 26 | 27 | ## end 28 | 29 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/OH.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | OH 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | O, H 9 | 10 | # Ediss [eV] 11 | 4.392 12 | 13 | # Fit type for partition function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | 19 | # pf_coef 20 | 4 3.0929 -1.6778 0.6743 -0.1874 21 | 22 | # eqc_coef 23 | 3 10.7881 -0.8762 -0.5436 24 | 25 | # Filenames with line data 26 | ## /home/uitenbr/src/rh_v2/Molecules/OH/OH_X-X_1565.zeeman 27 | ## end 28 | -------------------------------------------------------------------------------- /Source/Background.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_BACKGROUND_HPP 2 | #define CMO_BACKGROUND_HPP 3 | 4 | #include "Lightweaver.hpp" 5 | #include "Constants.hpp" 6 | #include "CmoArray.hpp" 7 | 8 | struct BackgroundData 9 | { 10 | F64View chPops; 11 | F64View ohPops; 12 | F64View h2Pops; 13 | F64View hMinusPops; 14 | F64View2D hPops; 15 | 16 | F64View wavelength; 17 | F64View2D chi; 18 | F64View2D eta; 19 | F64View2D scatt; 20 | }; 21 | 22 | void basic_background(BackgroundData* bd, Atmosphere* atmos, 23 | int laStart=-1, int laEnd=-1); 24 | f64 Gaunt_bf(f64 lambda, f64 n_eff, int charge); 25 | 26 | #endif -------------------------------------------------------------------------------- /Source/LightweaverAmalgamated.cpp: -------------------------------------------------------------------------------- 1 | #define CMO_FORMAL_INTERFACE_IMPL 2 | #include "Lightweaver.hpp" 3 | 4 | #include "FormalInterface.cpp" 5 | #include "LuSolve.cpp" 6 | #include "Atmosphere.cpp" 7 | #include "Background.cpp" 8 | #include "FastBackground.cpp" 9 | #include "FormalScalar.cpp" 10 | #include "FormalScalar2d.cpp" 11 | #include "FormalStokes.cpp" 12 | #include "UpdatePopulations.cpp" 13 | #include "Prd.cpp" 14 | #include "EscapeProbability.cpp" 15 | #include "ThreadStorage.cpp" 16 | 17 | // NOTE(cmo): This file does a lot of includes, #defines, and usings that I'm not super keen on, so I suggest leaving it last 18 | #include "Faddeeva.cc" -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/CaH.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | CAH 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | Ca, H 9 | 10 | # Ediss [eV] 11 | 1.7000 12 | 13 | # Fit type for partion function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | 19 | # pf_coef 20 | 4 3.8411 -2.3891 1.3578 -0.6893 21 | 22 | # eqc_coef 23 | 5 9.1952 -0.8802 1.1610 -2.5221 1.3514 24 | 25 | # Filenames with line data 26 | 27 | # /home/uitenbr/src/rh_v2/Molecules/CaH/cah.asc 28 | 29 | ## end 30 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/H2.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | H2 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | 2H 9 | 10 | # Ediss [eV] 11 | 4.478 12 | 13 | # Fit type for partion function and equilibrium constant 14 | KURUCZ_85 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | 19 | # pf_coef 20 | 7 0.582145 16.3760 -49.4684 112.049 -149.953 106.531 -30.9791 21 | 22 | # eqc_coef 23 | 7 -46.4584 16.3660 -49.3992 111.822 -149.567 106.206 -30.8720 24 | 25 | # Filenames with line data 26 | ## ../../Molecules/H2/H2_X-B.DAT 27 | ## end 28 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/CO_NLTE.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | CO 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | C, O 9 | 10 | # Ediss [eV] 11 | 11.091 12 | 13 | # Fit type for partion function and equilibrium constant 14 | KURUCZ_85 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 12.0E+3 18 | 19 | # pf_coef 20 | 7 4.51349 18.4221 -50.0599 102.208 -128.504 87.8414 -24.8533 21 | 22 | # eqc_coef 23 | 6 -49.0414 14.0306 -26.6341 35.3827 -26.5424 8.32385 24 | 25 | # Filename with line data 26 | 27 | ../../Molecules/CO/vmax=9_Jmax=120_dv=1_26 28 | 29 | ## end 30 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/HF.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | HF 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | H, F 9 | 10 | # Ediss [eV] 11 | 5.869 12 | 13 | # Fit type for partion function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | 19 | # pf_coef 20 | 4 2.4164 -1.6132 0.6357 -0.1767 21 | 22 | # eqc_coef 23 | 3 11.2835 -0.9228 -0.5642 24 | 25 | # Filenames with line data 26 | 27 | ## /home/uitenbr/src/rh_v2/Molecules/HF/HF_X-X.asc 28 | /Users/uitenbr/Source/rh_v2/Molecules/HF/HF_X-X.zeeman 29 | ## end 30 | -------------------------------------------------------------------------------- /Source/LwIterationResult.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LW_ITERATION_RESULT_HPP 2 | #define CMO_LW_ITERATION_RESULT_HPP 3 | #include "Constants.hpp" 4 | #include 5 | 6 | struct IterationResult 7 | { 8 | bool updatedJ; 9 | f64 dJMax; 10 | int dJMaxIdx; 11 | 12 | bool updatedPops; 13 | std::vector dPops; 14 | std::vector dPopsMaxIdx; 15 | bool ngAccelerated; 16 | 17 | bool updatedNe; 18 | f64 dNe; 19 | int dNeMaxIdx; 20 | 21 | bool updatedRho; 22 | std::vector dRho; 23 | std::vector dRhoMaxIdx; 24 | int NprdSubIter; 25 | bool updatedJPrd; 26 | std::vector dJPrdMax; 27 | std::vector dJPrdMaxIdx; 28 | }; 29 | 30 | #endif -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/il13n10.dat: -------------------------------------------------------------------------------- 1 | 0.012340063235641409 59.808147756847937160 34.279506101270783347 2 | 0.011977753205880952 78.440397299188958868 9.261508421721341477 3 | 0.014450026269801278 54.706216284681580930 4.861938489116336193 4 | 0.013253956366881255 77.940749858091649571 76.676630372745506747 5 | 0.010201119400831222 79.965404704142571291 30.453809153154836054 6 | 0.011938568025220213 79.489960595520855691 53.121190524054888726 7 | 0.010235973115032470 14.399989259039207568 18.558399092700515354 8 | 0.012959288726089032 32.742524961589239751 76.558708586790046979 9 | 0.014092902977591758 55.828542773190278581 62.961304364897543451 10 | 0.013550348677030402 36.577133490670270533 33.584446718406461230 11 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/CN.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | CN 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | C, N 9 | 10 | # Ediss [eV] 11 | 7.760 12 | 13 | # Fit type for partion function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | 19 | # pf_coef 20 | 4 4.0078 -2.1514 0.9226 -0.1671 21 | 22 | # eqc_coef 23 | 5 11.4479 -0.4840 -0.4160 -0.9435 0.8380 24 | 25 | # Filenames with line data 26 | 27 | ## /home/uitenbr/src/rh_v2/Molecules/CN/CN_X-A.asc 28 | ## /home/uitenbr/src/rh_v2/Molecules/CN/CN_X-B.asc 29 | 30 | ## end 31 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/CO.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | CO 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | C, O 9 | 10 | # Ediss [eV] 11 | 11.091 12 | 13 | # Fit type for partion function and equilibrium constant 14 | KURUCZ_85 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 12.0E+3 18 | 19 | # pf_coef 20 | 7 4.51349 18.4221 -50.0599 102.208 -128.504 87.8414 -24.8533 21 | 22 | # eqc_coef 23 | 6 -49.0414 14.0306 -26.6341 35.3827 -26.5424 8.32385 24 | 25 | # Filename with line data (Pick one) 26 | 27 | ## ../../Molecules/CO/goorvitch.dv1_26 28 | ## ../../Molecules/CO/vmax=9_Jmax=120_dv=1_26 29 | 30 | ## end 31 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/TiO.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | TiO 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | TI, O 9 | 10 | # Ediss [eV] 11 | 6.87 12 | 13 | # Fit type for partion function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | 19 | # pf_coef 20 | 4 5.3051 -2.3739 0.8940 -0.3641 21 | 22 | # eqc_coef 23 | 4 11.4047 -1.1484 0.6478 -0.6737 24 | 25 | # Filenames with line data 26 | ## ../../Molecules/TiO/TiO_schwenke_5379-5381.asc 27 | ## ../../Molecules/TiO/TiO_schwenke_5575-5577.asc 28 | ../../Molecules/TiO/TiO_schwenke_7089_7092.asc 29 | 30 | ## end 31 | -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/il15n11.dat: -------------------------------------------------------------------------------- 1 | 0.011309124685216070 53.315719928593146903 84.610163655558565665 2 | 0.009652307741108415 81.382467213182962951 62.110163655558558560 3 | 0.013486249257448579 34.127332208658117452 62.110163655558544349 4 | 0.011644240437362686 15.578524934891703424 17.110163655558558560 5 | 0.012045724827157482 37.579916693977011732 17.110163655558569218 6 | 0.012838609802518424 78.016940549443560826 84.610163655558551454 7 | 0.011977125696312044 64.924968260607656134 17.110163655558555007 8 | 0.012838609802518421 78.016940549443560826 39.610163655558558560 9 | 0.011309124685216068 53.315719928593146903 39.610163655558558560 10 | 0.010715526695526693 61.289485196402061717 62.110163655558544349 11 | 0.007183356369615116 85.965248921746891142 17.110163655558551454 12 | -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/iql11n11.dat: -------------------------------------------------------------------------------- 1 | 0.013928998075380375 76.933155506464885320 80.768819942956795899 2 | 0.011639454305572013 50.160253002625303509 35.768819942956781688 3 | 0.011029475400118194 57.530476539597316332 58.268819942956810110 4 | 0.011177779148407979 79.762325286908392741 58.268819942956788793 5 | 0.011639454305572000 50.160253002625303509 80.768819942956824320 6 | 0.011499122313789287 59.678966258514456911 13.268819942956803004 7 | 0.012557626403854785 28.786346749828133795 58.268819942956788793 8 | 0.011968777966644249 30.896072585800268939 13.268819942956804780 9 | 0.005651235410063879 8.118127482350042001 13.268819942956803004 10 | 0.013928998075380379 76.933155506464885320 35.768819942956795899 11 | 0.009979078595216876 81.295129582612020158 13.268819942956806557 12 | -------------------------------------------------------------------------------- /Source/CmoArrayHelper.pyx: -------------------------------------------------------------------------------- 1 | from CmoArray cimport F64View, F64View2D, F64View3D, F64View4D 2 | from CmoArrayHelper cimport f64 3 | 4 | cdef F64View f64_view(f64[::1] memview) except +: 5 | if memview.shape[0] == 0: 6 | return F64View() 7 | return F64View(&memview[0], memview.shape[0]) 8 | 9 | cdef F64View2D f64_view_2(f64[:, ::1] memview) except +: 10 | return F64View2D(&memview[0,0], memview.shape[0], memview.shape[1]) 11 | 12 | cdef F64View3D f64_view_3(f64[:, :, ::1] memview) except +: 13 | return F64View3D(&memview[0,0,0], memview.shape[0], memview.shape[1], memview.shape[2]) 14 | 15 | cdef F64View4D f64_view_4(f64[:, :, :, ::1] memview) except +: 16 | return F64View4D(&memview[0,0,0,0], memview.shape[0], memview.shape[1], memview.shape[2], memview.shape[3]) -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/CH.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | CH 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | C, H 9 | 10 | # Ediss [eV] 11 | 3.465 12 | 13 | # Fit type for partion function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | 16 | # Tmin and Tmax [K] 17 | 1.0E+3 9.0E+3 18 | ## 1.0E+3 12.0E+3 19 | 20 | # pf_coef 21 | 4 3.3586 -2.0656 0.9624 -0.2239 22 | 23 | # eqc_coef 24 | 3 10.5263 -0.5284 -0.7239 25 | 26 | # Filenames with line data 27 | 28 | ## ../../Molecules/CH/CH_X-A_12.asc 29 | ## ../../Molecules/CH/CH_X-B_12.asc 30 | ## /home/uitenbr/src/rh_v2/Molecules/CH/Joergensen_CH_X-A.dat 31 | ## /home/uitenbr/src/rh_v2/Molecules/CH/Joergensen_CH_X-B.dat 32 | 33 | 34 | ## ../../Molecules/CH/CH_X-A.zeeman 35 | ## end 36 | -------------------------------------------------------------------------------- /lightweaver/Data/DefaultMolecules/MgH.molecule: -------------------------------------------------------------------------------- 1 | # <> 2 | MGH 3 | 4 | # Charge 5 | 0 6 | 7 | # List of constituent atoms of the form nA, mB, pC for molecule AmBnCp 8 | Mg, H 9 | 10 | # Ediss [eV] 11 | 1.3400 12 | 13 | # Fit type for partion function and equilibrium constant 14 | SAUVAL_TATUM_84 15 | ## IRWIN_81 16 | 17 | # Tmin and Tmax [K] 18 | 1.0E+3 15.0E+3 19 | 20 | # pf_coef SAUVAL & TATUM 84 21 | 4 3.6704 -2.2682 0.9354 -0.2597 22 | 23 | # pf_coef IRWIN 81 24 | ## 6 -2.53112027E+02 1.72996315E+02 -4.65953970E+01 6.25577718E+00 -4.16915792E-01 1.10889210E-02 25 | 26 | # eqc_coef 27 | 5 9.2846 -0.3587 -0.1220 -1.5627 1.3047 28 | 29 | # Filenames with line data 30 | 31 | # /home/uitenbr/src/rh_v2/Molecules/MgH/MgH_X-A.asc 32 | # /home/uitenbr/src/rh_v2/Molecules/MgH/MgH_X-B.asc 33 | 34 | ## end 35 | -------------------------------------------------------------------------------- /Source/LwFormalInterfacePosix.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LW_FORMAL_INTERFACE_POSIX_HPP 2 | #define CMO_LW_FORMAL_INTERFACE_POSIX_HPP 3 | 4 | struct PlatformSharedLibrary 5 | { 6 | void* handle; 7 | }; 8 | 9 | #ifdef CMO_FORMAL_INTERFACE_IMPL 10 | #include 11 | 12 | namespace LwInternal 13 | { 14 | bool load_library(PlatformSharedLibrary* lib, const char* path) 15 | { 16 | void* handle = dlopen(path, RTLD_LAZY); 17 | if (!handle) 18 | { 19 | lib->handle = nullptr; 20 | return false; 21 | } 22 | lib->handle = handle; 23 | return true; 24 | } 25 | 26 | template 27 | F load_function(PlatformSharedLibrary lib, const char* name) 28 | { 29 | F f = (F)dlsym(lib.handle, name); 30 | 31 | return f; 32 | } 33 | 34 | void close_library(PlatformSharedLibrary lib) 35 | { 36 | dlclose(lib.handle); 37 | } 38 | } 39 | #endif 40 | #else 41 | #endif 42 | -------------------------------------------------------------------------------- /M1Notes.md: -------------------------------------------------------------------------------- 1 | ### Notes from experimenting with builds for M1 (cmo) 2 | 3 | Currently conda/miniconda do not support Apple M1. By default, their versions of Python run through Rosetta2, which does not support AVX extensions. For this reason I have enabled universal2 builds on Darwin, and removed the specialised flags that expected Sandy Bridge+ processors (i.e. AVX) on this platform. 4 | Whilst I have tested the generated Universal build through Rosetta2 on M1, I haven't tested the arm64 component, as I haven't had enough time to play with a true arm64 Python environnment, and assistance along these lines would be appreciated. 5 | 6 | The effect of removing these flags may somewhat slow down Lightweaver on some intel macs, ARM based macs are becoming more prominent, and this hopefully allows them to use the off the shelf binaries compiled on the CD system. 7 | 8 | If there is a more elegant way of doing this, I would love to hear about it. 9 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=64", 4 | "wheel", 5 | "setuptools_scm[toml]>=3.4", 6 | "cython", 7 | "numpy>=2.0" 8 | ] 9 | build-backend = 'setuptools.build_meta' 10 | 11 | [tool.setuptools_scm] 12 | write_to = 'lightweaver/version.py' 13 | 14 | [project] 15 | name = "lightweaver" 16 | dynamic = ["version"] 17 | requires-python = ">=3.10" 18 | authors = [ 19 | {name = "Chris Osborne", email = "lw@contextuallight.com"} 20 | ] 21 | maintainers = [ 22 | {name = "Chris Osborne", email = "lw@contextuallight.com"} 23 | ] 24 | description = "Non-LTE Radiative Transfer Framework in Python" 25 | readme = "README.md" 26 | license = "MIT" 27 | dependencies = [ 28 | "numpy>=2.0", 29 | "numba~=0.60", 30 | "matplotlib", 31 | "scipy", 32 | "parse", 33 | "specutils", 34 | "tqdm", 35 | "weno4", 36 | "pyyaml", 37 | "mda-xdrlib; python_version > '3.12'", 38 | ] 39 | 40 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/iql13n14.dat: -------------------------------------------------------------------------------- 1 | 0.009401985730130531 36.732189358482777664 50.944364924509983439 2 | 0.009724739249628363 57.713948833644813874 87.850191495075662829 3 | 0.007336385477289490 80.517824063429216608 60.283488540423768143 4 | 0.009364158915773805 36.153876883263549757 81.184129499677737840 5 | 0.010888116068585385 79.796790016643697641 36.088435334387689579 6 | 0.006645865611251284 19.612357748991122719 23.085967540413967924 7 | 0.006633848084763223 13.991000932110038590 67.990652119222275473 8 | 0.010488373826850328 80.181916954128965358 13.621353628460232343 9 | 0.010364129557869509 57.818007556642903921 64.893232102215506529 10 | 0.009137304992051887 40.305097780627185955 20.814435731139536045 11 | 0.010079305268725575 58.221787508566890779 41.135022928006904408 12 | 0.004602532138815957 77.086681783933542533 55.507469929084102489 13 | 0.011561448525557041 79.025919187451520997 81.410136440499854871 14 | 0.008771806552707621 61.006321387637633791 18.992374439000894171 15 | -------------------------------------------------------------------------------- /Source/TaskSetWrapper.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LW_TASK_SET_WRAPPER_HPP 2 | #define CMO_LW_TASK_SET_WRAPPER_HPP 3 | #include "TaskScheduler.h" 4 | #include 5 | 6 | typedef void(*TaskSetFn)(void* userdata, enki::TaskScheduler* s, 7 | enki::TaskSetPartition, uint32_t threadId); 8 | 9 | struct LwTaskSet : public enki::ITaskSet 10 | { 11 | LwTaskSet() = default; 12 | LwTaskSet(void* userdata_, enki::TaskScheduler* sched_, 13 | uint32_t setSize_, uint32_t minRange_, TaskSetFn fn_) 14 | : ITaskSet(setSize_, minRange_), 15 | taskFn(fn_), 16 | sched(sched_), 17 | userdata(userdata_) 18 | {} 19 | 20 | void ExecuteRange(enki::TaskSetPartition p, uint32_t threadId) override 21 | { 22 | if (!userdata) 23 | assert(false); 24 | taskFn(userdata, sched, p, threadId); 25 | } 26 | 27 | TaskSetFn taskFn; 28 | enki::TaskScheduler* sched; 29 | void* userdata; 30 | }; 31 | 32 | #else 33 | #endif -------------------------------------------------------------------------------- /lightweaver/constants.py: -------------------------------------------------------------------------------- 1 | 2 | CLight = 2.99792458E+08 # Speed of light [m/s] */ 3 | HPlanck = 6.6260755E-34 # Planck's constant [Js] */ 4 | HC = HPlanck * CLight 5 | HC_CM = HC * 1e2 # HC with c in cm/s, for wavenumbers as cm-1 6 | KBoltzmann = 1.380658E-23 # Boltzman's constant [J/K] */ 7 | Amu = 1.6605402E-27 # Atomic mass unit [kg] */ 8 | MElectron = 9.1093897E-31 # Electron mass [kg] */ 9 | QElectron = 1.60217733E-19 # Electron charge [C] */ 10 | Epsilon0 = 8.854187817E-12 # Vacuum permittivity [F/m] */ 11 | Mu0 = 1.2566370614E-06 # Magnetic induct. of vac. */ 12 | RBohr = 5.29177349E-11 # Bohr radius [m] */ 13 | ERydberg = 2.1798741E-18 # Ion. pot. Hydrogen [J] */ 14 | EV = 1.60217733E-19 # One electronVolt [J] */ 15 | Theta0 = 5.03974756E+03 # log10(e) * eV/k [K^-1] */ 16 | ABarH = 7.42E-41 # polarizability of Hydrogen [Fm^2] 17 | E_ION_HMIN = 0.754*EV 18 | 19 | NM_TO_M = 1.0E-09 20 | 21 | VMICRO_CHAR=3.0e3 22 | B_CHAR=0.0 #TESLA 23 | -------------------------------------------------------------------------------- /Source/LwContext.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LW_CONTEXT_HPP 2 | #define CMO_LW_CONTEXT_HPP 3 | 4 | #include "ThreadStorage.hpp" 5 | #include "LwInternal.hpp" 6 | #include "LwFormalInterface.hpp" 7 | #include "LwMisc.hpp" 8 | #include "LwAtmosphere.hpp" 9 | #include "LwTransition.hpp" 10 | #include "LwAtom.hpp" 11 | 12 | struct DepthData 13 | { 14 | bool fill; 15 | F64View4D chi; 16 | F64View4D eta; 17 | F64View4D I; 18 | }; 19 | 20 | struct Context 21 | { 22 | Atmosphere* atmos; 23 | Spectrum* spect; 24 | std::vector activeAtoms; 25 | std::vector detailedAtoms; 26 | Background* background; 27 | DepthData* depthData; 28 | int Nthreads; 29 | LwInternal::ThreadData threading; 30 | FormalSolver formalSolver; 31 | InterpFn interpFn; 32 | FsIterationFns iterFns; 33 | void* methodScratch; 34 | 35 | void initialise_threads() 36 | { 37 | threading.initialise(this); 38 | } 39 | 40 | void update_threads() 41 | { 42 | threading.clear(this); 43 | threading.initialise(this); 44 | } 45 | }; 46 | 47 | #else 48 | #endif -------------------------------------------------------------------------------- /Source/Dispatch_compute_full_operator_rates.ipp: -------------------------------------------------------------------------------- 1 | #include 2 | // NOTE(cmo): Machine-Generated! 3 | template 4 | inline auto dispatch_compute_full_operator_rates_(bool first, bool second, Args&& ...args) 5 | -> decltype(compute_full_operator_rates(std::forward(args)...)) 6 | { 7 | u32 dispatcher__ = first; 8 | dispatcher__ += second << 1; 9 | 10 | 11 | switch (dispatcher__) 12 | { 13 | case 0: 14 | { 15 | return compute_full_operator_rates(std::forward(args)...); 16 | } break; 17 | case 1: 18 | { 19 | return compute_full_operator_rates(std::forward(args)...); 20 | } break; 21 | case 2: 22 | { 23 | return compute_full_operator_rates(std::forward(args)...); 24 | } break; 25 | case 3: 26 | { 27 | return compute_full_operator_rates(std::forward(args)...); 28 | } break; 29 | 30 | default: 31 | { 32 | assert(false); 33 | } break; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Christopher M J Osborne 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/iql15n18.dat: -------------------------------------------------------------------------------- 1 | 0.005914024434459050 20.660032820026899003 42.946184169224324023 2 | 0.008596636090469512 80.011117440102310638 33.688427806802813791 3 | 0.007113673482367161 64.921995065345257103 72.010467664894790119 4 | 0.006827258788941489 64.147513396558437648 18.497227186025057222 5 | 0.006754747098551407 64.730319718050438382 0.919534448876960009 6 | 0.007479599563267462 39.674067680319787144 38.668043251384816017 7 | 0.007204332706475753 47.769294855310704406 83.111452685139369123 8 | 0.004857493646975331 10.141119789523482808 85.352194023546530843 9 | 0.007336667967350494 81.510142203036437536 87.342675972246922811 10 | 0.007086892105928068 81.785826761520155515 69.365943668747192419 11 | 0.006578810918075098 66.803811952801240182 53.058440530090045684 12 | 0.006617138696419558 28.211680236763250207 9.168537444166632966 13 | 0.007781933481283711 59.472237532728144060 36.552172143953320926 14 | 0.006596638597620959 31.588144273444552113 69.312358022053928153 15 | 0.007274106171534564 49.285923721302381750 59.213873405830867114 16 | 0.007120519412160358 46.203685118527786813 15.621163533242103938 17 | 0.007306722626653114 81.724670735547547906 15.505757262020685161 18 | 0.006552804211466908 82.370956679666591072 51.477865433759312452 19 | -------------------------------------------------------------------------------- /Source/Simd.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_SIMD_HPP 2 | #define CMO_SIMD_HPP 3 | 4 | #include "CmoArray.hpp" 5 | 6 | enum class SimdType 7 | { 8 | Scalar, 9 | SSE2, 10 | AVX2FMA, 11 | AVX512, 12 | NEON, 13 | SIMD_TYPE_COUNT 14 | }; 15 | 16 | constexpr inline 17 | bool SSE2_available() 18 | { 19 | // NOTE(cmo) This first attempt doesn't seem to work reliably for the 64-bit 20 | // compiler. 21 | // #if (defined(_WIN32) && _M_IX86_FP==2) || defined(__SSE2__) 22 | #if (defined(_WIN32) && (defined(_M_AMD64) || defined(_M_X64))) \ 23 | || defined(__SSE2__) 24 | return true; 25 | #else 26 | return false; 27 | #endif 28 | } 29 | 30 | constexpr inline 31 | bool AVX2FMA_available() 32 | { 33 | #if (defined(_WIN32) && defined(__AVX2__)) \ 34 | || (defined(__AVX2__) && defined(__FMA__)) 35 | return true; 36 | #else 37 | return false; 38 | #endif 39 | } 40 | 41 | constexpr inline 42 | bool AVX512_available() 43 | { 44 | #if defined(__AVX512F__) && defined(__AVX512DQ__) 45 | // NOTE(cmo): We use instructions from both AVX512F and AVX512DQ, I believe 46 | // where one is available, the other is too. 47 | return true; 48 | #else 49 | return false; 50 | #endif 51 | } 52 | 53 | constexpr int SimdWidth[(size_t)SimdType::SIMD_TYPE_COUNT] = {1, 2, 4, 8, 2}; 54 | 55 | #ifdef _WIN32 56 | #define ForceInline __forceinline 57 | #else 58 | #define ForceInline __attribute__((always_inline)) 59 | #endif 60 | 61 | #else 62 | #endif -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: BuildWheels 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build_sdist: 8 | name: Build SDist 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - uses: actions/setup-python@v5 14 | with: 15 | python-version: '3.11' 16 | 17 | - run: python -m pip install pep517 18 | - run: python -m pep517.build --source . 19 | 20 | - uses: actions/upload-artifact@v4 21 | with: 22 | name: artifact-sdist 23 | path: dist/*.tar.gz 24 | 25 | build_wheels: 26 | name: Wheel on ${{ matrix.os }} 27 | runs-on: ${{ matrix.os }} 28 | strategy: 29 | matrix: 30 | os: [ubuntu-22.04, windows-latest, macos-latest] 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - uses: actions/setup-python@v5 36 | with: 37 | python-version: '3.11' 38 | 39 | - run: python -m pip install cibuildwheel 40 | 41 | - run: python -m cibuildwheel --output-dir wheelhouse 42 | env: 43 | CIBW_BUILD: cp310-* cp311-* cp312-* cp312-* 44 | CIBW_SKIP: "*-musllinux_*" 45 | CIBW_ARCHS: auto64 46 | CIBW_ENVIRONMENT: 'LW_CI_BUILD=true' 47 | CIBW_BUILD_VERBOSITY: 1 48 | CIBW_ARCHS_MACOS: x86_64 universal2 49 | 50 | - uses: actions/upload-artifact@v4 51 | with: 52 | name: artifact-${{ matrix.os }} 53 | path: wheelhouse/*.whl 54 | -------------------------------------------------------------------------------- /Source/LwFormalInterfaceWin.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LW_FORMAL_INTERFACE_WIN_HPP 2 | #define CMO_LW_FORMAL_INTERFACE_WIN_HPP 3 | #ifndef WIN32_LEAN_AND_MEAN 4 | #define WIN32_LEAN_AND_MEAN 5 | #endif 6 | #ifndef NOMINMAX 7 | #define NOMINMAX 8 | #endif 9 | #include 10 | 11 | struct PlatformSharedLibrary 12 | { 13 | HMODULE handle; 14 | }; 15 | 16 | #ifdef CMO_FORMAL_INTERFACE_IMPL 17 | namespace LwInternal 18 | { 19 | bool load_library(PlatformSharedLibrary* lib, const char* path) 20 | { 21 | std::vector wPath; 22 | int wPathLen = MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, path, -1, wPath.data(), 0); 23 | wPath.resize(wPathLen); 24 | MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, path, -1, wPath.data(), wPathLen); 25 | int absPathLen = GetFullPathNameW(wPath.data(), 0, nullptr, nullptr); 26 | std::vector absPath(absPathLen); 27 | GetFullPathNameW(wPath.data(), absPathLen, absPath.data(), nullptr); 28 | HMODULE handle = LoadLibraryW(absPath.data()); 29 | if (!handle) 30 | { 31 | lib->handle = nullptr; 32 | return false; 33 | } 34 | lib->handle = handle; 35 | return true; 36 | } 37 | 38 | template 39 | F load_function(PlatformSharedLibrary lib, const char* name) 40 | { 41 | F f = (F)GetProcAddress(lib.handle, name); 42 | 43 | return f; 44 | } 45 | 46 | void close_library(PlatformSharedLibrary lib) 47 | { 48 | FreeLibrary(lib.handle); 49 | } 50 | } 51 | #endif 52 | #else 53 | #endif 54 | -------------------------------------------------------------------------------- /lightweaver/simd_management.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from typing import List 3 | 4 | from numpy.core._multiarray_umath import __cpu_features__ 5 | 6 | # NOTE(cmo): These are added in reverse order of preference (due to width), i.e. 7 | # try to use the key furthest down the list. 8 | LwSimdImplsAndFlags = { 9 | 'SSE2': ['SSE2'], 10 | 'AVX2FMA': ['AVX2', 'FMA3'], 11 | 'AVX512': ['AVX512F', 'AVX512DQ'] 12 | } 13 | 14 | def get_available_simd_suffixes() -> List[str]: 15 | ''' 16 | Verifies the necessary flags against the features NumPy indicates are 17 | available, and returns a list of available LightweaverSimdImpls 18 | ''' 19 | validExts = [] 20 | for impl, flags in LwSimdImplsAndFlags.items(): 21 | if all(__cpu_features__[flag] for flag in flags): 22 | validExts.append(impl) 23 | return validExts 24 | 25 | def filter_usable_simd_impls(implLibs: List[str]) -> List[str]: 26 | ''' 27 | Filter a list of SimdImpl library names, returning those that are usable on 28 | the current machine. 29 | ''' 30 | usableImpls = get_available_simd_suffixes() 31 | result = [] 32 | for lib in implLibs: 33 | _, libName = path.split(lib) 34 | # NOTE(cmo): A lib name is expected to be of the form 35 | # SimdImpl_{SimdType}.{pep3149}.so. So we split at the underscore and 36 | # check what the name starts with. 37 | nameEnd = libName.split('_')[1] 38 | for simdType in usableImpls: 39 | if nameEnd.startswith(simdType): 40 | result.append(lib) 41 | break 42 | return result 43 | -------------------------------------------------------------------------------- /lightweaver/__init__.py: -------------------------------------------------------------------------------- 1 | from .atmosphere import (Atmosphere, BoundaryCondition, Layout, NoBc, 2 | PeriodicRadiation, ScaleType, Stratifications, 3 | ThermalisedRadiation, ZeroRadiation) 4 | from .atomic_model import reconfigure_atom 5 | from .atomic_set import (RadiativeSet, SpectrumConfiguration, hminus_pops, 6 | lte_pops) 7 | from .atomic_table import (AtomicAbundance, DefaultAtomicAbundance, Element, 8 | Isotope, KuruczPfTable, PeriodicTable) 9 | from .benchmark import benchmark 10 | from .config import params as ConfigDict 11 | from .constants import * 12 | from .iterate_ctx import (ConvergenceCriteria, DefaultConvergenceCriteria, 13 | iterate_ctx_se) 14 | from .iteration_update import IterationUpdate 15 | from .LwCompiled import LwContext 16 | from .molecule import MolecularTable 17 | from .multi import read_multi_atmos 18 | from .nr_update import nr_post_update 19 | from .utils import (ConvergenceError, CrswIterator, ExplodingMatrixError, 20 | InitialSolution, NgOptions, UnityCrswIterator, air_to_vac, 21 | compute_contribution_fn, compute_height_edges, 22 | compute_radiative_losses, compute_wavelength_edges, 23 | convert_specific_intensity, gaunt_bf, get_data_path, 24 | get_default_molecule_path, integrate_line_losses, planck, 25 | vac_to_air, voigt_H) 26 | from .version import version as __version__ 27 | 28 | 29 | # NOTE(cmo): This is here to make it easier to retroactively monkeypatch 30 | class Context(LwContext): 31 | pass 32 | 33 | setattr(Context, 'nr_post_update', nr_post_update) 34 | -------------------------------------------------------------------------------- /.github/workflows/build_deploy.yml: -------------------------------------------------------------------------------- 1 | name: BuildAndDeploy 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: 7 | - published 8 | 9 | jobs: 10 | build_sdist: 11 | name: Build SDist 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - uses: actions/setup-python@v5 17 | with: 18 | python-version: '3.11' 19 | 20 | - run: python -m pip install pep517 21 | - run: python -m pep517.build --source . 22 | 23 | - uses: actions/upload-artifact@v4 24 | with: 25 | name: artifact-sdist 26 | path: dist/*.tar.gz 27 | 28 | build_wheels: 29 | name: Wheel on ${{ matrix.os }} 30 | runs-on: ${{ matrix.os }} 31 | strategy: 32 | matrix: 33 | os: [ubuntu-22.04, windows-latest, macos-latest] 34 | 35 | steps: 36 | - uses: actions/checkout@v4 37 | 38 | - uses: actions/setup-python@v5 39 | with: 40 | python-version: '3.11' 41 | 42 | - run: python -m pip install cibuildwheel 43 | 44 | - run: python -m cibuildwheel --output-dir wheelhouse 45 | env: 46 | CIBW_BUILD: cp310-* cp311-* cp312-* cp312-* 47 | CIBW_SKIP: "*-musllinux_*" 48 | CIBW_ARCHS: auto64 49 | CIBW_ENVIRONMENT: 'LW_CI_BUILD=true' 50 | CIBW_BUILD_VERBOSITY: 1 51 | CIBW_ARCHS_MACOS: x86_64 universal2 52 | 53 | - uses: actions/upload-artifact@v4 54 | with: 55 | name: artifact-${{ matrix.os }} 56 | path: wheelhouse/*.whl 57 | 58 | deploy: 59 | needs: [build_sdist, build_wheels] 60 | runs-on: ubuntu-latest 61 | 62 | steps: 63 | - uses: actions/download-artifact@v4 64 | with: 65 | merge-multiple: true 66 | pattern: artifact-* 67 | path: dist 68 | 69 | - uses: pypa/gh-action-pypi-publish@release/v1 70 | with: 71 | user: __token__ 72 | password: ${{ secrets.pypi_token }} 73 | -------------------------------------------------------------------------------- /Source/LwExtraParams.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LW_EXTRA_PARAMS_HPP 2 | #define CMO_LW_EXTRA_PARAMS_HPP 3 | #include "CmoArray.hpp" 4 | #include "Constants.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | typedef std::variant, 14 | Jasnah::Array2NonOwn, 15 | Jasnah::Array3NonOwn, 16 | Jasnah::Array4NonOwn, 17 | Jasnah::Array5NonOwn, 18 | F64View1D, 19 | F64View2D, 20 | F64View3D, 21 | F64View4D, 22 | F64View5D> 23 | Variant; 24 | 25 | struct ExtraParams 26 | { 27 | std::unordered_map map; 28 | 29 | bool contains(const std::string& key) const 30 | { 31 | auto iter = map.find(key); 32 | return iter != map.end(); 33 | } 34 | 35 | template 36 | void insert(const std::string& key, T value) 37 | { 38 | map.insert_or_assign(key, value); 39 | } 40 | 41 | template 42 | T& get_as(const std::string& key) 43 | { 44 | // NOTE(cmo): This can throw from either step here. 45 | auto& var = map.at(key); 46 | // NOTE(cmo): We can't use std::get on a Variant on older macOS because 47 | // it depends on implementation in libc++ (see 48 | // https://stackoverflow.com/a/53887048/3847013). 49 | // We'll use get_if instead, which is apparently fine. 50 | // return std::get(var); 51 | if (auto* p = std::get_if(&var)) 52 | return *p; 53 | 54 | throw std::runtime_error("Bad Variant type/index access."); 55 | } 56 | 57 | Variant get(const std::string& key) 58 | { 59 | auto iter = map.find(key); 60 | if (iter == map.end()) 61 | return Variant{}; // returns an "empty" Variant 62 | 63 | return iter->second; 64 | } 65 | }; 66 | #else 67 | #endif -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | print(sys.executable) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'Lightweaver' 21 | copyright = '2022, C. Osborne' 22 | author = 'C. Osborne' 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 29 | # ones. 30 | extensions = ['sphinx.ext.napoleon', 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', 31 | 'sphinx_gallery.gen_gallery' 32 | ] 33 | 34 | sphinx_gallery_conf = { 35 | 'examples_dirs': '../examples', 36 | 'gallery_dirs': 'auto_examples' 37 | } 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # List of patterns, relative to source directory, that match files and 43 | # directories to ignore when looking for source files. 44 | # This pattern also affects html_static_path and html_extra_path. 45 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 46 | 47 | 48 | # -- Options for HTML output ------------------------------------------------- 49 | 50 | # The theme to use for HTML and HTML Help pages. See the documentation for 51 | # a list of builtin themes. 52 | # 53 | html_theme = 'sphinx_rtd_theme' 54 | 55 | # Add any paths that contain custom static files (such as style sheets) here, 56 | # relative to this directory. They are copied after the builtin static files, 57 | # so a file named "default.css" will overwrite the builtin "default.css". 58 | html_static_path = [] 59 | -------------------------------------------------------------------------------- /Source/FastBackground.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_FAST_BACKGROUND_HPP 2 | #define CMO_FAST_BACKGROUND_HPP 3 | #include "Lightweaver.hpp" 4 | #include "CmoArray.hpp" 5 | #include "TaskScheduler.h" 6 | #include "TaskSetWrapper.hpp" 7 | #include "Utils.hpp" 8 | #include 9 | 10 | struct BackgroundContinuum 11 | { 12 | int i; 13 | int j; 14 | int laStart; 15 | int laEnd; 16 | F64View alpha; 17 | 18 | BackgroundContinuum(int i_, int j_, f64 minLa, f64 laEdge, 19 | F64View crossSection, 20 | F64View globalWavelength) 21 | : i(i_), 22 | j(j_), 23 | laStart(), 24 | laEnd(), 25 | alpha(crossSection) 26 | { 27 | laStart = hunt(globalWavelength, minLa); 28 | laEnd = hunt(globalWavelength, laEdge) + 1; 29 | } 30 | }; 31 | 32 | struct ResonantRayleighLine 33 | { 34 | f64 Aji; 35 | f64 gRatio; // g_j / g_0 36 | f64 lambda0; 37 | f64 lambdaMax; 38 | 39 | ResonantRayleighLine(f64 A, f64 gjgi, f64 lambda0, f64 lambdaMax) 40 | : Aji(A), 41 | gRatio(gjgi), 42 | lambda0(lambda0), 43 | lambdaMax(lambdaMax) 44 | {} 45 | }; 46 | 47 | struct BackgroundAtom 48 | { 49 | F64View2D n; 50 | F64View2D nStar; 51 | std::vector continua; 52 | std::vector resonanceScatterers; 53 | }; 54 | 55 | struct FastBackgroundContext 56 | { 57 | int Nthreads; 58 | enki::TaskScheduler sched; 59 | FastBackgroundContext() : Nthreads(), 60 | sched() 61 | {} 62 | 63 | ~FastBackgroundContext() 64 | { 65 | sched.WaitforAllAndShutdown(); 66 | } 67 | 68 | void initialise(int numThreads) 69 | { 70 | Nthreads = numThreads; 71 | if (numThreads <= 1) 72 | return; 73 | sched.Initialize(Nthreads); 74 | } 75 | 76 | void basic_background(BackgroundData* bd, Atmosphere* atmosphere); 77 | void bf_opacities(BackgroundData* bd, std::vector* atoms, 78 | Atmosphere* atmos); 79 | void rayleigh_scatter(BackgroundData* bd, std::vector* atoms, 80 | Atmosphere* atmos); 81 | 82 | 83 | }; 84 | 85 | 86 | #endif -------------------------------------------------------------------------------- /.github/workflows/build_docs.yml: -------------------------------------------------------------------------------- 1 | name: BuildDocs 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: 7 | - published 8 | 9 | jobs: 10 | make_docs: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | # NOTE(cmo): fetch-tags is broken when this is triggered by a tag... 16 | # https://github.com/actions/checkout/issues/1467 17 | with: 18 | # fetch-tags: true 19 | fetch-depth: 0 20 | 21 | # NOTE(cmo): Manually fetch tags 22 | - name: Fetch tags 23 | run: git fetch --tags 24 | 25 | - uses: actions/setup-python@v5 26 | with: 27 | python-version: '3.12' 28 | 29 | - name: Setup venv 30 | run: | 31 | python -m venv lw-docs-venv 32 | source lw-docs-venv/bin/activate 33 | python -m pip install sphinx sphinx-rtd-theme sphinx-gallery 34 | python -m pip install -v . 35 | env: 36 | LW_CI_BUILD: false 37 | 38 | - name: Build docs 39 | working-directory: ./docs 40 | run: | 41 | source ../lw-docs-venv/bin/activate 42 | make html 43 | touch _build/html/.nojekyll 44 | 45 | - name: Pack docs 46 | run: tar -cvf docs.tar docs/_build/html 47 | 48 | - uses: actions/upload-artifact@v4 49 | with: 50 | name: docs 51 | path: docs.tar 52 | 53 | deploy_docs: 54 | needs: make_docs 55 | runs-on: ubuntu-latest 56 | 57 | steps: 58 | - uses: actions/checkout@v4 59 | with: 60 | ref: gh-pages 61 | 62 | - uses: actions/download-artifact@v4 63 | with: 64 | name: docs 65 | path: NewDocs 66 | 67 | - name: Remove old and unpack docs 68 | run: | 69 | rm -r docs 70 | cd NewDocs 71 | tar -xvf docs.tar 72 | 73 | - name: Move docs 74 | run: cp -a --verbose NewDocs/docs/_build/html docs 75 | 76 | - name: Commit docs 77 | run: | 78 | git config --local user.name "github-actions[bot]" 79 | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" 80 | git add docs 81 | # This is a slightly messy line to only push if the commit succeeded 82 | # (because there was changes, but to not throw an error if there was no 83 | # changes.) 84 | (git commit -m "Autogenerated new docs" && git push) || true 85 | -------------------------------------------------------------------------------- /Source/Atmosphere.cpp: -------------------------------------------------------------------------------- 1 | #include "Lightweaver.hpp" 2 | #include "Constants.hpp" 3 | #include 4 | 5 | void Atmosphere::update_projections() 6 | { 7 | switch (Ndim) 8 | { 9 | case 1: 10 | { 11 | for (int mu = 0; mu < Nrays; ++mu) 12 | { 13 | for (int k = 0; k < Nspace; ++k) 14 | { 15 | vlosMu(mu, k) = muz(mu) * vz(k); 16 | } 17 | } 18 | } break; 19 | 20 | case 2: 21 | { 22 | for (int mu = 0; mu < Nrays; ++mu) 23 | { 24 | for (int k = 0; k < Nspace; ++k) 25 | { 26 | vlosMu(mu, k) = mux(mu) * vx(k) + muz(mu) * vz(k); 27 | } 28 | } 29 | } break; 30 | 31 | case 3: 32 | { 33 | for (int mu = 0; mu < Nrays; ++mu) 34 | { 35 | for (int k = 0; k < Nspace; ++k) 36 | { 37 | vlosMu(mu, k) = mux(mu) * vx(k) + muy(mu) * vy(k) + muz(mu) * vz(k); 38 | } 39 | } 40 | } break; 41 | 42 | default: 43 | { 44 | } break; 45 | } 46 | 47 | if (!B) 48 | return; 49 | 50 | for (int mu = 0; mu < Nrays; ++mu) 51 | { 52 | if (muz(mu) == 1.0) 53 | { 54 | for (int k = 0; k < Nspace; ++k) 55 | { 56 | cosGamma(mu, k) = cos(gammaB(k)); 57 | cos2chi(mu, k) = cos(2.0 * chiB(k)); 58 | sin2chi(mu, k) = sin(2.0 * chiB(k)); 59 | } 60 | } 61 | else 62 | { 63 | f64 cscTheta = 1.0 / sqrt(1.0 - square(muz(mu))); 64 | for (int k = 0; k < Nspace; ++k) 65 | { 66 | // NOTE(cmo): Basic projection using spherical polar 67 | // coordinates. 68 | f64 sinGamma = sin(gammaB(k)); 69 | f64 bx = sinGamma * cos(chiB(k)); 70 | f64 by = sinGamma * sin(chiB(k)); 71 | f64 bz = cos(gammaB(k)); 72 | 73 | f64 b3 = mux(mu)*bx + muy(mu)*by + muz(mu)*bz; 74 | f64 b1 = cscTheta * (bz - muz(mu)*b3); 75 | f64 b2 = cscTheta * (muy(mu)*bx - mux(mu)*by); 76 | 77 | cosGamma(mu, k) = b3; 78 | cos2chi(mu, k) = (square(b1) - square(b2)) / (1.0 - square(b3)); 79 | sin2chi(mu, k) = 2.0 * b1*b2 / (1.0 - square(b3)); 80 | } 81 | } 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /Source/Utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LW_UTILS_HPP 2 | #define CMO_LW_UTILS_HPP 3 | #include "Constants.hpp" 4 | #include "CmoArray.hpp" 5 | #include "JasPP.hpp" 6 | #include 7 | 8 | template 9 | inline int hunt(int len, T first, U val) 10 | { 11 | auto last = first + len; 12 | auto it = std::upper_bound(first, last, val) - 1; 13 | return it - first; 14 | } 15 | 16 | inline int hunt(F64View x, f64 val) 17 | { 18 | return hunt(x.dim0, x.data, val); 19 | } 20 | 21 | inline void linear(F64View xTable, F64View yTable, F64View x, F64View y) 22 | { 23 | const int Ntable = xTable.shape(0); 24 | const int N = x.shape(0); 25 | bool ascend = xTable(1) > xTable(0); 26 | const f64 xMin = If ascend Then xTable(0) Else xTable(Ntable-1) End; 27 | const f64 xMax = If ascend Then xTable(Ntable-1) Else xTable(0) End; 28 | 29 | for (int n = 0; n < N; ++n) 30 | { 31 | if (x(n) <= xMin) 32 | y(n) = If ascend Then yTable(0) Else yTable(Ntable-1) End; 33 | else if (x(n) >= xMax) 34 | y(n) = If ascend Then yTable(Ntable-1) Else yTable(0) End; 35 | else 36 | { 37 | int j = hunt(xTable, x(n)); 38 | 39 | f64 fx = (xTable(j+1) - x(n)) / (xTable(j+1) - xTable(j)); 40 | y(n) = fx * yTable(j) + (1 - fx) * yTable(j+1); 41 | } 42 | } 43 | } 44 | 45 | inline f64 linear(F64View xTable, F64View yTable, f64 x) 46 | { 47 | const int Ntable = xTable.shape(0); 48 | bool ascend = xTable(1) > xTable(0); 49 | const f64 xMin = If ascend Then xTable(0) Else xTable(Ntable-1) End; 50 | const f64 xMax = If ascend Then xTable(Ntable-1) Else xTable(0) End; 51 | 52 | if (x <= xMin) 53 | return If ascend Then yTable(0) Else yTable(Ntable-1) End; 54 | 55 | if (x >= xMax) 56 | return If ascend Then yTable(Ntable-1) Else yTable(0) End; 57 | 58 | int j = hunt(xTable, x); 59 | 60 | f64 fx = (xTable(j+1) - x) / (xTable(j+1) - xTable(j)); 61 | return fx * yTable(j) + (1 - fx) * yTable(j+1); 62 | } 63 | 64 | inline f64 bilinear(int Ncol, int Nrow, const f64 *f, f64 x, f64 y) { 65 | int i, j, i1, j1; 66 | double fx, fy; 67 | 68 | /* --- Bilinear interpolation of the function f on the fractional 69 | indices x and y -- -------------- */ 70 | 71 | i = (int)x; 72 | fx = x - i; 73 | if (i == Ncol - 1) 74 | i1 = i; 75 | else 76 | i1 = i + 1; 77 | j = (int)y; 78 | fy = y - j; 79 | if (j == Nrow - 1) 80 | j1 = j; 81 | else 82 | j1 = j + 1; 83 | 84 | return (1.0 - fx) * (1.0 - fy) * f[j * Ncol + i] + 85 | fx * (1.0 - fy) * f[j * Ncol + i1] + 86 | (1.0 - fx) * fy * f[j1 * Ncol + i] + fx * fy * f[j1 * Ncol + i1]; 87 | } 88 | 89 | 90 | #else 91 | #endif -------------------------------------------------------------------------------- /Source/Faddeeva.hh: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2012 Massachusetts Institute of Technology 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining 4 | * a copy of this software and associated documentation files (the 5 | * "Software"), to deal in the Software without restriction, including 6 | * without limitation the rights to use, copy, modify, merge, publish, 7 | * distribute, sublicense, and/or sell copies of the Software, and to 8 | * permit persons to whom the Software is furnished to do so, subject to 9 | * the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be 12 | * included in all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | /* Available at: http://ab-initio.mit.edu/Faddeeva 24 | 25 | Header file for Faddeeva.cc; see that file for more information. */ 26 | 27 | #ifndef FADDEEVA_HH 28 | #define FADDEEVA_HH 1 29 | 30 | #include 31 | 32 | namespace Faddeeva { 33 | 34 | // compute w(z) = exp(-z^2) erfc(-iz) [ Faddeeva / scaled complex error func ] 35 | extern std::complex w(std::complex z,double relerr=0); 36 | extern double w_im(double x); // special-case code for Im[w(x)] of real x 37 | 38 | // Various functions that we can compute with the help of w(z) 39 | 40 | // compute erfcx(z) = exp(z^2) erfc(z) 41 | extern std::complex erfcx(std::complex z, double relerr=0); 42 | extern double erfcx(double x); // special case for real x 43 | 44 | // compute erf(z), the error function of complex arguments 45 | extern std::complex erf(std::complex z, double relerr=0); 46 | extern double erf(double x); // special case for real x 47 | 48 | // compute erfi(z) = -i erf(iz), the imaginary error function 49 | extern std::complex erfi(std::complex z, double relerr=0); 50 | extern double erfi(double x); // special case for real x 51 | 52 | // compute erfc(z) = 1 - erf(z), the complementary error function 53 | extern std::complex erfc(std::complex z, double relerr=0); 54 | extern double erfc(double x); // special case for real x 55 | 56 | // compute Dawson(z) = sqrt(pi)/2 * exp(-z^2) * erfi(z) 57 | extern std::complex Dawson(std::complex z, double relerr=0); 58 | extern double Dawson(double x); // special case for real x 59 | 60 | } // namespace Faddeeva 61 | 62 | #endif // FADDEEVA_HH 63 | -------------------------------------------------------------------------------- /Source/Dispatch_chi_eta_aux_accum.ipp: -------------------------------------------------------------------------------- 1 | #include 2 | // NOTE(cmo): Machine-Generated! 3 | template 4 | inline auto dispatch_chi_eta_aux_accum_(bool first, bool second, bool third, bool fourth, Args&& ...args) 5 | -> decltype(chi_eta_aux_accum(std::forward(args)...)) 6 | { 7 | u32 dispatcher__ = first; 8 | dispatcher__ += second << 1; 9 | dispatcher__ += third << 2; 10 | dispatcher__ += fourth << 3; 11 | 12 | 13 | switch (dispatcher__) 14 | { 15 | case 0: 16 | { 17 | return chi_eta_aux_accum(std::forward(args)...); 18 | } break; 19 | case 1: 20 | { 21 | return chi_eta_aux_accum(std::forward(args)...); 22 | } break; 23 | case 2: 24 | { 25 | return chi_eta_aux_accum(std::forward(args)...); 26 | } break; 27 | case 3: 28 | { 29 | return chi_eta_aux_accum(std::forward(args)...); 30 | } break; 31 | case 4: 32 | { 33 | return chi_eta_aux_accum(std::forward(args)...); 34 | } break; 35 | case 5: 36 | { 37 | return chi_eta_aux_accum(std::forward(args)...); 38 | } break; 39 | case 6: 40 | { 41 | return chi_eta_aux_accum(std::forward(args)...); 42 | } break; 43 | case 7: 44 | { 45 | return chi_eta_aux_accum(std::forward(args)...); 46 | } break; 47 | case 8: 48 | { 49 | return chi_eta_aux_accum(std::forward(args)...); 50 | } break; 51 | case 9: 52 | { 53 | return chi_eta_aux_accum(std::forward(args)...); 54 | } break; 55 | case 10: 56 | { 57 | return chi_eta_aux_accum(std::forward(args)...); 58 | } break; 59 | case 11: 60 | { 61 | return chi_eta_aux_accum(std::forward(args)...); 62 | } break; 63 | case 12: 64 | { 65 | return chi_eta_aux_accum(std::forward(args)...); 66 | } break; 67 | case 13: 68 | { 69 | return chi_eta_aux_accum(std::forward(args)...); 70 | } break; 71 | case 14: 72 | { 73 | return chi_eta_aux_accum(std::forward(args)...); 74 | } break; 75 | case 15: 76 | { 77 | return chi_eta_aux_accum(std::forward(args)...); 78 | } break; 79 | 80 | default: 81 | { 82 | assert(false); 83 | } break; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Source/Dispatch_intensity_core_opt.ipp: -------------------------------------------------------------------------------- 1 | #include 2 | // NOTE(cmo): Machine-Generated! 3 | template 4 | inline auto dispatch_intensity_core_opt_(bool first, bool second, bool third, bool fourth, Args&& ...args) 5 | -> decltype(intensity_core_opt(std::forward(args)...)) 6 | { 7 | u32 dispatcher__ = first; 8 | dispatcher__ += second << 1; 9 | dispatcher__ += third << 2; 10 | dispatcher__ += fourth << 3; 11 | 12 | 13 | switch (dispatcher__) 14 | { 15 | case 0: 16 | { 17 | return intensity_core_opt(std::forward(args)...); 18 | } break; 19 | case 1: 20 | { 21 | return intensity_core_opt(std::forward(args)...); 22 | } break; 23 | case 2: 24 | { 25 | return intensity_core_opt(std::forward(args)...); 26 | } break; 27 | case 3: 28 | { 29 | return intensity_core_opt(std::forward(args)...); 30 | } break; 31 | case 4: 32 | { 33 | return intensity_core_opt(std::forward(args)...); 34 | } break; 35 | case 5: 36 | { 37 | return intensity_core_opt(std::forward(args)...); 38 | } break; 39 | case 6: 40 | { 41 | return intensity_core_opt(std::forward(args)...); 42 | } break; 43 | case 7: 44 | { 45 | return intensity_core_opt(std::forward(args)...); 46 | } break; 47 | case 8: 48 | { 49 | return intensity_core_opt(std::forward(args)...); 50 | } break; 51 | case 9: 52 | { 53 | return intensity_core_opt(std::forward(args)...); 54 | } break; 55 | case 10: 56 | { 57 | return intensity_core_opt(std::forward(args)...); 58 | } break; 59 | case 11: 60 | { 61 | return intensity_core_opt(std::forward(args)...); 62 | } break; 63 | case 12: 64 | { 65 | return intensity_core_opt(std::forward(args)...); 66 | } break; 67 | case 13: 68 | { 69 | return intensity_core_opt(std::forward(args)...); 70 | } break; 71 | case 14: 72 | { 73 | return intensity_core_opt(std::forward(args)...); 74 | } break; 75 | case 15: 76 | { 77 | return intensity_core_opt(std::forward(args)...); 78 | } break; 79 | 80 | default: 81 | { 82 | assert(false); 83 | } break; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Source/LwMisc.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LW_MISC_HPP 2 | #define CMO_LW_MISC_HPP 3 | 4 | #include "CmoArray.hpp" 5 | #include "Constants.hpp" 6 | #include "Faddeeva.hh" 7 | 8 | typedef View BoolView; 9 | typedef Arr BoolArr; // Avoid the dreaded vector 10 | typedef View I32View; 11 | typedef Arr I32Arr; 12 | 13 | inline f64 voigt_H(f64 a, f64 v) 14 | { 15 | using Faddeeva::w; 16 | using namespace std::complex_literals; 17 | auto z = (v + a * 1i); 18 | return w(z).real(); 19 | } 20 | 21 | inline std::complex voigt_HF(f64 a, f64 v) 22 | { 23 | using Faddeeva::w; 24 | using namespace std::complex_literals; 25 | auto z = (v + a * 1i); 26 | return w(z); 27 | } 28 | 29 | inline void planck_nu(int Nspace, f64* T, f64 lambda, f64* Bnu) 30 | { 31 | namespace C = Constants; 32 | constexpr f64 hc_k = C::HC / (C::KBoltzmann * C::NM_TO_M); 33 | const f64 hc_kla = hc_k / lambda; 34 | constexpr f64 twoh_c2 = (2.0 * C::HC) / cube(C::NM_TO_M); 35 | const f64 twohnu3_c2 = twoh_c2 / cube(lambda); 36 | constexpr f64 MAX_EXPONENT = 150.0; 37 | 38 | for (int k = 0; k < Nspace; k++) 39 | { 40 | f64 hc_Tkla = hc_kla / T[k]; 41 | if (hc_Tkla <= MAX_EXPONENT) 42 | Bnu[k] = twohnu3_c2 / (exp(hc_Tkla) - 1.0); 43 | else 44 | Bnu[k] = 0.0; 45 | } 46 | } 47 | 48 | namespace Prd 49 | { 50 | struct RhoInterpCoeffs 51 | { 52 | int i0; 53 | int i1; 54 | f64 frac; 55 | RhoInterpCoeffs() : i0(0), i1(0), frac(0.0) {} 56 | RhoInterpCoeffs(int idx0, int idx1, f64 f) : i0(idx0), i1(idx1), frac(f) {} 57 | }; 58 | struct JInterpCoeffs 59 | { 60 | f64 frac; 61 | int idx; 62 | JInterpCoeffs() : frac(0.0), idx(0) {} 63 | JInterpCoeffs(int i, f64 f) : frac(f), idx(i) {} 64 | }; 65 | typedef Jasnah::Array4Own RhoCoeffVec; 66 | typedef Jasnah::Array4NonOwn RhoCoeffView; 67 | typedef Jasnah::Array4Own> JCoeffVec; 68 | 69 | struct PrdStorage 70 | { 71 | bool upToDate = false; 72 | Jasnah::Array2Own gII; 73 | Jasnah::Array2Own qWave; 74 | Prd::RhoCoeffVec hPrdCoeffs; 75 | }; 76 | } 77 | 78 | namespace PrdCores 79 | { 80 | constexpr int max_fine_grid_size(); 81 | } 82 | 83 | struct Background 84 | { 85 | F64View2D chi; 86 | F64View2D eta; 87 | F64View2D sca; 88 | }; 89 | 90 | struct Spectrum 91 | { 92 | F64View wavelength; 93 | F64View3D I; 94 | F64View4D Quv; 95 | F64View2D J; 96 | BoolArr prdActive; 97 | std::vector prdIdxs; 98 | BoolArr hPrdActive; 99 | std::vector hPrdIdxs; 100 | I32Arr la_to_prdLa; 101 | I32Arr la_to_hPrdLa; 102 | Prd::JCoeffVec JCoeffs; 103 | F64Arr2D JRest; 104 | }; 105 | 106 | struct ZeemanComponents 107 | { 108 | I32View alpha; 109 | F64View shift; 110 | F64View strength; 111 | }; 112 | 113 | 114 | #else 115 | #endif -------------------------------------------------------------------------------- /Source/FormalInterface.cpp: -------------------------------------------------------------------------------- 1 | #include "LwFormalInterface.hpp" 2 | #include "LwInternal.hpp" 3 | #include "Lightweaver.hpp" 4 | #include 5 | #include 6 | 7 | using namespace LwInternal; 8 | 9 | bool FormalSolverManager::load_fs_from_path(const char* path) 10 | { 11 | PlatformSharedLibrary lib{}; 12 | if (!load_library(&lib, path)) 13 | { 14 | return false; 15 | } 16 | 17 | libs.emplace_back(lib); 18 | 19 | FsProvider fs_provider = load_function(lib, "fs_provider"); 20 | if (!fs_provider) 21 | { 22 | return false; 23 | } 24 | 25 | FormalSolver fs = fs_provider(); 26 | formalSolvers.emplace_back(fs); 27 | return true; 28 | } 29 | 30 | FormalSolverManager::FormalSolverManager() 31 | { 32 | formalSolvers.emplace_back(FormalSolver{piecewise_linear_1d, 1, 1, "piecewise_linear_1d"}); 33 | formalSolvers.emplace_back(FormalSolver{piecewise_besser_1d, 1, 1, "piecewise_besser_1d"}); 34 | formalSolvers.emplace_back(FormalSolver{piecewise_bezier3_1d, 1, 1, "piecewise_bezier3_1d"}); 35 | formalSolvers.emplace_back(FormalSolver{piecewise_linear_2d, 2, 1, "piecewise_linear_2d"}); 36 | formalSolvers.emplace_back(FormalSolver{piecewise_besser_2d, 2, 1, "piecewise_besser_2d"}); 37 | } 38 | 39 | InterpFnManager::InterpFnManager() 40 | { 41 | fns.emplace_back(InterpFn(2, "interp_linear_2d", interp_linear_2d)); 42 | fns.emplace_back(InterpFn(2, "interp_besser_2d", interp_besser_2d)); 43 | } 44 | 45 | bool InterpFnManager::load_fn_from_path(const char* path) 46 | { 47 | PlatformSharedLibrary lib{}; 48 | if (!load_library(&lib, path)) 49 | return false; 50 | 51 | libs.emplace_back(lib); 52 | 53 | InterpProvider interp_provider = load_function(lib, "interp_fn_provider"); 54 | if (!interp_provider) 55 | return false; 56 | 57 | InterpFn interp = interp_provider(); 58 | fns.emplace_back(interp); 59 | return true; 60 | } 61 | 62 | bool FsIterationFnsManager::load_fns_from_path(const char* path) 63 | { 64 | PlatformSharedLibrary lib{}; 65 | if (!load_library(&lib, path)) 66 | { 67 | return false; 68 | } 69 | 70 | libs.emplace_back(lib); 71 | 72 | auto fs_provider = load_function(lib, "fs_iteration_fns_provider"); 73 | if (!fs_provider) 74 | { 75 | return false; 76 | } 77 | 78 | FsIterationFns fs = fs_provider(); 79 | fns.emplace_back(fs); 80 | return true; 81 | } 82 | 83 | FsIterationFnsManager::FsIterationFnsManager() 84 | { 85 | fns.emplace_back(FsIterationFns{ 86 | -1, false, true, true, true, 87 | "mali_full_precond_scalar", 88 | formal_sol_iteration_matrices_scalar, 89 | formal_sol_scalar, 90 | formal_sol_full_stokes_impl, 91 | redistribute_prd_lines_scalar, 92 | stat_eq_impl, 93 | time_dependent_update_impl, 94 | nr_post_update_impl 95 | }); 96 | } -------------------------------------------------------------------------------- /Source/Lightweaver.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LIGHTWEAVER_HPP 2 | #define CMO_LIGHTWEAVER_HPP 3 | 4 | #include "LwMisc.hpp" 5 | #include "LwAtmosphere.hpp" 6 | #include "LwTransition.hpp" 7 | #include "LwAtom.hpp" 8 | #include "LwContext.hpp" 9 | #include "LwIterationResult.hpp" 10 | #include "LwExtraParams.hpp" 11 | 12 | struct NrTimeDependentData 13 | { 14 | f64 dt; 15 | std::vector nPrev; 16 | }; 17 | 18 | IterationResult formal_sol_gamma_matrices(Context& ctx, bool lambdaIterate=false, ExtraParams params=ExtraParams{}); 19 | IterationResult formal_sol_iteration_matrices_scalar(Context& ctx, bool lambdaIterate=false, ExtraParams params=ExtraParams{}); 20 | IterationResult formal_sol(Context& ctx, bool upOnly=true, ExtraParams params=ExtraParams{}); 21 | IterationResult formal_sol_scalar(Context& ctx, bool upOnly=true, ExtraParams params=ExtraParams{}); 22 | IterationResult formal_sol_full_stokes(Context& ctx, bool updateJ=false, 23 | bool upOnly=true, 24 | ExtraParams params=ExtraParams{}); 25 | IterationResult formal_sol_full_stokes_impl(Context& ctx, bool updateJ=false, 26 | bool upOnly=true, 27 | ExtraParams params=ExtraParams{}); 28 | IterationResult redistribute_prd_lines(Context& ctx, int maxIter, f64 tol, ExtraParams params=ExtraParams{}); 29 | IterationResult redistribute_prd_lines_scalar(Context& ctx, int maxIter, f64 tol, ExtraParams params=ExtraParams{}); 30 | void stat_eq(Context& ctx, Atom* atom, ExtraParams params=ExtraParams{}, 31 | int spaceStart=-1, int spaceEnd=-1); 32 | void stat_eq_impl(Atom* atom, ExtraParams params=ExtraParams{}, 33 | int spaceStart=-1, int spaceEnd=-1); 34 | void time_dependent_update(Context& ctx, Atom* atomIn, F64View2D nOld, f64 dt, 35 | ExtraParams params=ExtraParams{}, 36 | int spaceStart=-1, int spaceEnd=-1); 37 | void time_dependent_update_impl(Atom* atomIn, F64View2D nOld, f64 dt, 38 | ExtraParams params=ExtraParams{}, 39 | int spaceStart=-1, int spaceEnd=-1); 40 | void nr_post_update(Context& ctx, std::vector* atoms, 41 | const std::vector& dC, 42 | F64View backgroundNe, 43 | const NrTimeDependentData& timeDepData, 44 | f64 crswVal, 45 | ExtraParams params=ExtraParams{}, 46 | int spaceStart=-1, int spaceEnd=-1); 47 | void nr_post_update_impl(Context& ctx, std::vector* atoms, 48 | const std::vector& dC, 49 | F64View backgroundNe, 50 | const NrTimeDependentData& timeDepData, 51 | f64 crswVal, 52 | ExtraParams params=ExtraParams{}, 53 | int spaceStart=-1, int spaceEnd=-1); 54 | void configure_hprd_coeffs(Context& ctx, bool includeDetailedAtoms=false); 55 | 56 | namespace EscapeProbability 57 | { 58 | void gamma_matrices_escape_prob(Atom* a, Background& background, 59 | const Atmosphere& atmos); 60 | } 61 | 62 | 63 | #else 64 | #endif -------------------------------------------------------------------------------- /Source/LuSolve.cpp: -------------------------------------------------------------------------------- 1 | #include "LuSolve.hpp" 2 | #include "Constants.hpp" 3 | #include "LwMisc.hpp" 4 | #include 5 | #include 6 | 7 | 8 | void lu_decompose(F64View2D A, I32View index, f64* d) 9 | { 10 | constexpr f64 Tiny = 1e-20; 11 | const int N = A.shape(0); 12 | auto vv = F64Arr(N); 13 | *d = 1.0; 14 | 15 | for (int i = 0; i < N; ++i) 16 | { 17 | f64 big = 0.0; 18 | for (int j = 0; j < N; ++j) 19 | { 20 | big = max(big, abs(A(i,j))); 21 | } 22 | if (big == 0.0) 23 | throw std::runtime_error("Singular Matrix"); 24 | 25 | vv(i) = 1.0 / big; 26 | } 27 | 28 | for (int j = 0; j < N; ++j) 29 | { 30 | for (int i = 0; i < j; ++i) 31 | { 32 | f64 sum = A(i,j); 33 | for (int k = 0; k < i; ++k) 34 | sum -= A(i,k) * A(k,j); 35 | A(i,j) = sum; 36 | } 37 | 38 | int iMax = 0; 39 | f64 big = 0.0; 40 | for (int i = j; i < N; ++i) 41 | { 42 | f64 sum = A(i,j); 43 | for (int k = 0; k < j; ++k) 44 | sum -= A(i,k) * A(k,j); 45 | A(i,j) = sum; 46 | big = max_idx(big, vv(i) * abs(sum), iMax, i); 47 | } 48 | if (j != iMax) 49 | { 50 | for (int k = 0; k < N; ++k) 51 | { 52 | f64 temp = A(iMax, k); 53 | A(iMax, k) = A(j, k); 54 | A(j, k) = temp; 55 | } 56 | *d = -(*d); 57 | vv(iMax) = vv(j); 58 | } 59 | index(j) = iMax; 60 | if (A(j,j) == 0.0) 61 | A(j,j) = Tiny; 62 | 63 | if (j != N) 64 | { 65 | f64 temp = 1.0 / A(j,j); 66 | for (int i = j + 1; i < N; ++i) 67 | A(i,j) *= temp; 68 | } 69 | } 70 | } 71 | 72 | void lu_backsub(F64View2D A, I32View index, F64View b) 73 | { 74 | const int N = A.shape(0); 75 | 76 | int ii = -1; 77 | for (int i = 0; i < N; ++i) 78 | { 79 | int ip = index(i); 80 | f64 sum = b(ip); 81 | b(ip) = b(i); 82 | if (ii >= 0) 83 | { 84 | for (int j = ii; j < i; ++j) 85 | sum -= A(i,j) * b(j); 86 | } 87 | else if (sum != 0.0) 88 | { 89 | ii = i; 90 | } 91 | b(i) = sum; 92 | } 93 | 94 | for (int i = N - 1; i >= 0; --i) 95 | { 96 | f64 sum = b(i); 97 | for (int j = i + 1; j < N; ++j) 98 | sum -= A(i,j) * b(j); 99 | b(i) = sum / A(i,i); 100 | } 101 | } 102 | 103 | void solve_lin_eq(F64View2D A, F64View b, bool improve) 104 | { 105 | const int N = A.shape(0); 106 | 107 | F64Arr1D residual; 108 | F64Arr2D ACopy; 109 | F64Arr1D bCopy; 110 | 111 | if (improve) 112 | { 113 | ACopy = A; 114 | bCopy = b; 115 | } 116 | 117 | f64 d; 118 | auto index = I32Arr(N); 119 | lu_decompose(A, index, &d); 120 | 121 | lu_backsub(A, index, b); 122 | if (improve) 123 | { 124 | residual = bCopy; 125 | for (int i = 0; i < N; ++i) 126 | for (int j = 0; j < N; ++j) 127 | residual(i) -= ACopy(i,j) * b(j); 128 | 129 | lu_backsub(A, index, residual); 130 | for (int i = 0; i < N; ++i) 131 | b(i) += residual(i); 132 | } 133 | } -------------------------------------------------------------------------------- /docs/lightweaver-api.rst: -------------------------------------------------------------------------------- 1 | Lightweaver API Documentation 2 | ============================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | lightweaver.atmosphere module 8 | ----------------------------- 9 | 10 | .. automodule:: lightweaver.atmosphere 11 | :members: 12 | :show-inheritance: 13 | 14 | lightweaver.atomic\_model module 15 | -------------------------------- 16 | 17 | .. automodule:: lightweaver.atomic_model 18 | :members: 19 | :show-inheritance: 20 | 21 | lightweaver.atomic\_set module 22 | ------------------------------ 23 | 24 | .. automodule:: lightweaver.atomic_set 25 | :members: 26 | :show-inheritance: 27 | 28 | lightweaver.atomic\_table module 29 | -------------------------------- 30 | 31 | .. automodule:: lightweaver.atomic_table 32 | :members: 33 | :show-inheritance: 34 | 35 | lightweaver.barklem module 36 | -------------------------- 37 | 38 | .. automodule:: lightweaver.barklem 39 | :members: 40 | :show-inheritance: 41 | 42 | lightweaver.benchmark module 43 | ------------------------------------- 44 | 45 | .. automodule:: lightweaver.benchmark 46 | :members: 47 | :show-inheritance: 48 | 49 | lightweaver.broadening module 50 | ----------------------------- 51 | 52 | .. automodule:: lightweaver.broadening 53 | :members: 54 | :show-inheritance: 55 | 56 | lightweaver.collisional\_rates module 57 | ------------------------------------- 58 | 59 | .. automodule:: lightweaver.collisional_rates 60 | :members: 61 | :show-inheritance: 62 | 63 | lightweaver.config module 64 | ------------------------------------- 65 | 66 | .. automodule:: lightweaver.config 67 | :members: 68 | :show-inheritance: 69 | 70 | lightweaver.fal module 71 | ---------------------- 72 | 73 | .. automodule:: lightweaver.fal 74 | :members: 75 | :show-inheritance: 76 | 77 | lightweaver.iterate_ctx module 78 | ------------------------------------- 79 | 80 | .. automodule:: lightweaver.iterate_ctx 81 | :members: 82 | :show-inheritance: 83 | 84 | lightweaver.iteration_update module 85 | ------------------------------------- 86 | 87 | .. automodule:: lightweaver.iteration_update 88 | :members: 89 | :show-inheritance: 90 | 91 | lightweaver.molecule module 92 | --------------------------- 93 | 94 | .. automodule:: lightweaver.molecule 95 | :members: 96 | :show-inheritance: 97 | 98 | lightweaver.multi module 99 | ------------------------ 100 | 101 | .. automodule:: lightweaver.multi 102 | :members: 103 | :show-inheritance: 104 | 105 | lightweaver.nr\_update module 106 | ----------------------------- 107 | 108 | .. automodule:: lightweaver.nr_update 109 | :members: 110 | :show-inheritance: 111 | 112 | lightweaver.rh\_atoms module 113 | ---------------------------- 114 | 115 | .. automodule:: lightweaver.rh_atoms 116 | :members: 117 | :show-inheritance: 118 | 119 | lightweaver.simd_management module 120 | ------------------------------------- 121 | 122 | .. automodule:: lightweaver.simd_management 123 | :members: 124 | :show-inheritance: 125 | 126 | lightweaver.utils module 127 | ------------------------ 128 | 129 | .. automodule:: lightweaver.utils 130 | :members: 131 | :show-inheritance: 132 | 133 | lightweaver.witt module 134 | ----------------------- 135 | 136 | .. automodule:: lightweaver.witt 137 | :members: 138 | :show-inheritance: 139 | 140 | lightweaver.zeeman module 141 | ------------------------- 142 | 143 | .. automodule:: lightweaver.zeeman 144 | :members: 145 | :show-inheritance: 146 | 147 | lightweaver.LwCompiled module 148 | ----------------------------- 149 | 150 | .. automodule:: lightweaver.LwCompiled 151 | :members: 152 | :undoc-members: 153 | :show-inheritance: 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lightweaver 2 | 3 | **C. Osborne (University of Glasgow) & I. Milić (NSO/CU Boulder), 2019-2021** 4 | 5 | **MIT License** 6 | 7 | Lightweaver is an NLTE radiative transfer code in the style of [RH](https://github.com/ITA-Solar/rh). 8 | It is well validated against RH and also [SNAPI](https://github.com/ivanzmilic/snapi). 9 | The code is currently designed for plane parallel atmospheres, either 1D single columns (which can be parallelised over wavelength) or 1.5D parallel columns with `ProcessPool` or MPI parallelisation. 10 | There is also support for unpolarised radiative transfer in 2D atmospheres. 11 | 12 | Lightweaver is described in a [paper (including examples!)](https://arxiv.org/abs/2107.00475), and has [API documentation](https://goobley.github.io/Lightweaver/). 13 | 14 | Whilst the core numerics are implemented in C++, as much of the non-performance critical code as possible is implemented in Python, and the code currently only has a Python interface (provided through a Cython binding module). 15 | Other languages with a C/C++ interface could interact directly with this core, hopefully allowing it to be reused as needed in different projects. 16 | 17 | The aim of Lightweaver is to provide an NLTE Framework, rather than a "code". 18 | That is to say, it should be more malleable, and provide easier access to experimentation, with most forms of experimentation (unless one wants to play with formal solvers or iteration schemes), being available directly from python. 19 | Formal solvers that comply with the interface defined in Lightweaver can be compiled into separate shared libraries and then loaded at runtime. 20 | The preceding concepts are inspired by the popular python machine learning frameworks such as PyTorch and Tensorflow. 21 | 22 | ## Installation 23 | 24 | For most users precompiled python wheels (supporting modern Linux, Mac, and Windows 10 systems) can be installed from `pip` and are the easiest way to get started with Lightweaver. 25 | Lightweaver requires python 3.8+, and it is recommended to be run inside a virtual environment using `conda`. 26 | In this case a new virtual environment can be created with: 27 | ``` 28 | conda create -n Lightweaver python=3.8 29 | ``` 30 | activate the environment: 31 | ``` 32 | conda activate Lightweaver 33 | ``` 34 | and Lightweaver can then be installed with 35 | ``` 36 | python -m pip install lightweaver 37 | ``` 38 | 39 | ### Installation from source 40 | 41 | Whilst the above should work for most people, if you wish to work on the Lightweaver backend it is beneficial to have a source installation. 42 | This requires a compiler supporting C++17. 43 | The build is then run with `python3 -m pip install -vvv -e .`. 44 | The libraries currently produce a few warnings, but should not produce any errors. 45 | 46 | ## Documentation 47 | 48 | - [Paper](https://arxiv.org/abs/2107.00475). 49 | - [API documentation](https://goobley.github.io/Lightweaver/). 50 | - I suggest looking through [the samples repository](https://github.com/Goobley/LightweaverSamples) (in particular the `Simple*.py`) after the code description in the paper to gain an understanding of the basic functionality and interfaces. 51 | These samples are unfortunately not always up to date, but are a work in progress. 52 | - The [MsLightweaver repository](https://github.com/Goobley/MsLightweaver) contains a more "production grade" tool built on Lightweaver for reprocessing the time-dependent radiative output from RADYN simulations. 53 | This tool is currently undocumented, but has a relatively simple structure. 54 | 55 | Please contact me through this repository if difficulties are encountered. 56 | 57 | ## Acknowledgements 58 | 59 | The [python implementation](https://github.com/jaimedelacruz/witt) of the Wittmann equation of state has been kindly provided J. de la Cruz Rodriguez. -------------------------------------------------------------------------------- /Source/Constants.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CONSTANTS_HPP 2 | #define CONSTANTS_HPP 3 | 4 | namespace Constants 5 | { 6 | // Speed of light [m/s] 7 | constexpr double CLight = 2.99792458E+08; 8 | // Planck's constant [Js] 9 | constexpr double HPlanck = 6.6260755E-34; 10 | constexpr double HC = HPlanck * CLight; 11 | // Boltzman's constant [J/K] 12 | constexpr double KBoltzmann = 1.380658E-23; 13 | // Atomic mass unit [kg] 14 | constexpr double Amu = 1.6605402E-27; 15 | // Electron mass [kg] 16 | constexpr double MElectron = 9.1093897E-31; 17 | // Electron charge [C] 18 | constexpr double QElectron = 1.60217733E-19 ; 19 | // Vacuum permittivity [F/m] 20 | constexpr double Epsilon0 = 8.854187817E-12; 21 | // Magnetic induct. of vac. 22 | constexpr double Mu0 = 1.2566370614E-06; 23 | // Bohr radius [m] 24 | constexpr double RBohr = 5.29177349E-11; 25 | // Ion. pot. Hydrogen [J] 26 | constexpr double ERydberg = 2.1798741E-18; 27 | // One electronVolt [J] 28 | constexpr double EV = 1.60217733E-19; 29 | // log10(e) * eV/k [K^-1] 30 | constexpr double Theta0 = 5.03974756E+03; 31 | // polarizability of Hydrogen [Fm^2] 32 | constexpr double ABarH = 7.42E-41; 33 | // pi -- the circle constant 34 | constexpr double Pi = 3.14159265358979323846264338327950288; 35 | // ln(10) 36 | constexpr double Log10 = 2.30258509299404568401799145468436421; 37 | // Ionization energy Hmin in [J] 38 | constexpr double E_ION_HMIN = 0.754*EV; 39 | 40 | constexpr double NM_TO_M = 1.0E-09; 41 | constexpr double CM_TO_M = 1.0E-02; 42 | constexpr double KM_TO_M = 1.0E+03; 43 | constexpr double ERG_TO_JOULE = 1.0E-07; 44 | constexpr double G_TO_KG = 1.0E-03; 45 | constexpr double MICRON_TO_NM = 1.0E+03; 46 | constexpr double MEGABARN_TO_M2 = 1.0E-22; 47 | } 48 | 49 | #ifndef CMO_NO_TYPEDEFS 50 | #include 51 | #include 52 | #include 53 | typedef int8_t i8; 54 | typedef int16_t i16; 55 | #ifdef _WIN32 56 | // NOTE(cmo): Fix for silly Windows types and int not being compatible with 57 | // long (which is correct due to C++ standard, but npy_int32 is defined to 58 | // long on Win32 and we need compatibility). 59 | typedef long i32; 60 | #else 61 | typedef int32_t i32; 62 | #endif 63 | typedef int64_t i64; 64 | typedef long long int longboi; 65 | typedef float f32; 66 | typedef double f64; 67 | typedef long double f128; 68 | typedef uint8_t u8; 69 | typedef uint16_t u16; 70 | typedef uint32_t u32; 71 | typedef uint64_t u64; 72 | typedef unsigned uint; 73 | using std::abs; 74 | using std::sin; 75 | using std::cos; 76 | using std::tan; 77 | using std::pow; 78 | using std::sqrt; 79 | #endif 80 | 81 | #ifndef CMO_NO_MATH 82 | template 83 | constexpr 84 | T square(T val) 85 | { 86 | return val * val; 87 | } 88 | 89 | template 90 | constexpr 91 | T cube(T val) 92 | { 93 | return val * val * val; 94 | } 95 | 96 | #include 97 | using std::min; 98 | using std::max; 99 | 100 | // template 101 | // constexpr 102 | // T max(T a, T b) 103 | // { 104 | // return a < b ? b : a; 105 | // } 106 | 107 | // template 108 | // constexpr 109 | // T min(T a, T b) 110 | // { 111 | // return a < b ? a : b; 112 | // } 113 | 114 | template 115 | constexpr 116 | T max_idx(T a, T b, U& aIdx, U bIdx) 117 | { 118 | if (a < b) 119 | { 120 | aIdx = bIdx; 121 | return b; 122 | } 123 | else 124 | return a; 125 | } 126 | 127 | template 128 | constexpr 129 | T min_idx(T a, T b, U& aIdx, U bIdx) 130 | { 131 | if (a < b) 132 | { 133 | return a; 134 | } 135 | else 136 | { 137 | aIdx = bIdx; 138 | return b; 139 | } 140 | } 141 | #endif 142 | 143 | #else 144 | #endif -------------------------------------------------------------------------------- /lightweaver/config.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from copy import copy 3 | from os import path 4 | from typing import List, Optional 5 | 6 | import astropy.config as conf 7 | import yaml 8 | from lightweaver.simd_management import (LwSimdImplsAndFlags, 9 | get_available_simd_suffixes) 10 | 11 | Defaults = { 12 | 'FormalSolver1d': 'piecewise_bezier3_1d', 13 | 'FormalSolver2d': 'piecewise_besser_2d', 14 | 'IterationScheme': 'mali_full_precond', 15 | 'SimdImpl': 'scalar', 16 | } 17 | 18 | params = copy(Defaults) 19 | 20 | def get_home_config_path() -> str: 21 | ''' 22 | Return the location where the user's configuration data *should* be stored, 23 | whether it is currently present or not. 24 | ''' 25 | confDir = conf.get_config_dir('lightweaver') 26 | homePath = path.join(confDir, 'lightweaverrc') 27 | return homePath 28 | 29 | 30 | def get_config_path() -> Optional[str]: 31 | ''' 32 | Returns the path to the `lightweaverrc` configuration file, or None if one 33 | cannot be found. 34 | ''' 35 | localPath = 'lightweaverrc' 36 | if path.isfile(localPath): 37 | return localPath 38 | homePath = get_home_config_path() 39 | if path.isfile(homePath): 40 | return homePath 41 | 42 | return None 43 | 44 | 45 | def set_most_advanced_simd_impl(): 46 | ''' 47 | Picks the most advanced SIMD extensions (as detected by NumPy), that can be 48 | used on this system. This does not guarantee fastest (see the note in 49 | `update_config_dict`). 50 | ''' 51 | availableImpls = get_available_simd_suffixes() 52 | 53 | def check_add_impl(simdType): 54 | if simdType in availableImpls: 55 | params['SimdImpl'] = simdType 56 | 57 | for impl in LwSimdImplsAndFlags: 58 | check_add_impl(impl) 59 | 60 | 61 | def update_config_dict(configPath: Optional[str]): 62 | ''' 63 | Updates the configuration dict (`lightweaver.ConfigDict`), from the config 64 | file. If there is no config file, the defaults are used, and the most 65 | advanced instruction set is chosen for the SimdImpl. If the SimdImpl in the 66 | config file is too advanced for the current CPU, the maximum available is 67 | chosen. 68 | 69 | Parameters 70 | ---------- 71 | configPath : str, optional 72 | The path to the config file, or None. 73 | ''' 74 | if configPath is None: 75 | warnings.warn('No config file found, using defaults. For optimised vectorised code,' 76 | ' please run `lightweaver.benchmark()`, otherwise the most advanced' 77 | ' instruction set supported by your machine will be picked, which may' 78 | ' not be the fastest (due to e.g. aggressive AVX offsets).') 79 | set_most_advanced_simd_impl() 80 | return 81 | 82 | with open(configPath, 'r') as f: 83 | confDict = yaml.safe_load(f) 84 | params.update(confDict) 85 | 86 | availableSimd : List[str] = get_available_simd_suffixes() 87 | if params['SimdImpl'] not in ['scalar'] + availableSimd: 88 | set_most_advanced_simd_impl() 89 | warnings.warn('SimdImpl was set to an overly advanced instruction set for the ' 90 | 'current CPU, setting to the maximum supported by your CPU.') 91 | 92 | 93 | def update_config_file(configPath: str): 94 | ''' 95 | Updates the config file to the current values of the config dict. 96 | 97 | Parameters 98 | ---------- 99 | configPath : str 100 | The path to the config file. 101 | ''' 102 | with open(configPath, 'w') as f: 103 | yaml.safe_dump(params, f) 104 | 105 | 106 | update_config_dict(get_config_path()) 107 | -------------------------------------------------------------------------------- /lightweaver/multi.py: -------------------------------------------------------------------------------- 1 | import re 2 | from dataclasses import dataclass 3 | from typing import Tuple 4 | 5 | import astropy.units as u 6 | import numpy as np 7 | 8 | import lightweaver.constants as C 9 | from .atmosphere import Atmosphere, ScaleType 10 | 11 | 12 | @dataclass 13 | class MultiMetadata: 14 | ''' 15 | Metadata that is stored in a MULTI atmosphere, but doesn't really belong 16 | in a Lightweaver atmosphere. 17 | ''' 18 | name: str 19 | logG: float 20 | 21 | def read_multi_atmos(filename: str) -> Tuple[MultiMetadata, Atmosphere]: 22 | ''' 23 | Load a MULTI atmosphere definition from a file for use in Lightweaver. 24 | 25 | Parameters 26 | ---------- 27 | filename : str 28 | The path to load the file from. 29 | 30 | Returns 31 | ------- 32 | meta : MultiMetadata 33 | Additional metadata from the MULTI metadata that doesn't appear in a 34 | Lightweaver atmosphere (name, gravitational acceleration used). 35 | atmos : Atmosphere 36 | The atmosphere loaded into the standard Lightweaver format. 37 | 38 | Raises 39 | ------ 40 | ValueError 41 | if file isn't found, or cannot be parsed correctly. 42 | ''' 43 | try: 44 | with open(filename, 'r') as f: 45 | lines = f.readlines() 46 | except FileNotFoundError: 47 | raise ValueError('Atmosphere file not found (%s)' % filename) 48 | 49 | def get_line(commentPattern=r'^\s*\*'): 50 | while len(lines) > 0: 51 | line = lines.pop(0) 52 | if not re.match(commentPattern, line): 53 | return line.strip() 54 | return None 55 | 56 | atmosName = get_line() 57 | 58 | scaleStr = get_line() 59 | logG = float(get_line()) - 2 # For conversion to log[m.s^-2] 60 | Nspace = int(get_line()) 61 | 62 | dscale = np.zeros(Nspace) 63 | temp = np.zeros(Nspace) 64 | ne = np.zeros(Nspace) 65 | vlos = np.zeros(Nspace) 66 | vturb = np.zeros(Nspace) 67 | for k in range(Nspace): 68 | vals = get_line().split() 69 | vals = [float(v) for v in vals] 70 | dscale[k] = vals[0] 71 | temp[k] = vals[1] 72 | ne[k] = vals[2] 73 | vlos[k] = vals[3] 74 | vturb[k] = vals[4] 75 | 76 | scaleMode = scaleStr[0].upper() 77 | if scaleMode == 'M': 78 | scaleType = ScaleType.ColumnMass 79 | dscale = ((10**dscale) << u.Unit('g cm-2')).to('kg m-2').value 80 | elif scaleMode == 'T': 81 | scaleType = ScaleType.Tau500 82 | dscale = 10**dscale 83 | elif scaleMode == 'H': 84 | scaleType = ScaleType.Geometric 85 | dscale = (dscale << u.Unit('km')).to('m').value 86 | else: 87 | raise ValueError('Unknown scale type: %s (expected M, T, or H)' % scaleStr) 88 | 89 | vlos = (vlos << u.Unit('km s-1')).to('m s-1').value 90 | vturb = (vturb << u.Unit('km s-1')).to('m s-1').value 91 | ne = (ne << u.Unit('cm-3')).to('m-3').value 92 | 93 | if len(lines) <= Nspace: 94 | raise ValueError('Hydrogen populations not supplied!') 95 | 96 | hPops = np.zeros((6, Nspace)) 97 | for k in range(Nspace): 98 | vals = get_line().split() 99 | vals = [float(v) for v in vals] 100 | hPops[:, k] = vals 101 | 102 | hPops = (hPops << u.Unit('cm-3')).to('m-3').value 103 | 104 | meta = MultiMetadata(atmosName, logG) 105 | atmos = Atmosphere.make_1d(scale=scaleType, 106 | depthScale=dscale, 107 | temperature=temp, 108 | vlos=vlos, 109 | vturb=vturb, 110 | ne=ne, 111 | hydrogenPops=hPops) 112 | 113 | return (meta, atmos) 114 | -------------------------------------------------------------------------------- /lightweaver/Data/Barklem_pddata.dat: -------------------------------------------------------------------------------- 1 | c 2 | c p-d data from Barklem and O'Mara 1997, MNRAS, 290, 102 3 | c 4 | 425 461 507 566 630 706 799 889 995 1083 1191 1334 1478 1608 1790 1870 1936 2140 5 | 429 460 505 565 633 704 795 896 985 1082 1199 1340 1487 1611 1795 1872 1937 2136 6 | 419 451 501 556 627 700 785 891 977 1088 1212 1346 1493 1604 1793 1863 1930 2144 7 | 402 437 489 544 614 695 779 875 975 1102 1221 1350 1488 1591 1774 1844 1919 2126 8 | 384 418 467 529 595 674 769 856 976 1108 1224 1338 1467 1570 1743 1817 1900 2118 9 | 366 397 443 505 576 651 755 841 973 1095 1210 1308 1435 1545 1702 1786 1878 2081 10 | 356 387 432 489 562 635 722 841 961 1078 1175 1273 1397 1517 1672 1763 1863 2034 11 | 359 388 431 479 545 624 707 834 943 1059 1158 1256 1368 1490 1647 1747 1849 1998 12 | 361 394 436 483 547 615 704 817 920 1027 1124 1238 1358 1465 1624 1736 1838 1978 13 | 400 382 440 489 546 610 690 817 897 998 1115 1201 1351 1453 1599 1728 1829 1953 14 | 474 461 416 491 549 612 701 806 883 974 1078 1194 1310 1456 1569 1716 1818 1925 15 | 531 518 507 463 547 615 694 784 881 958 1047 1153 1297 1432 1547 1688 1809 1901 16 | 594 585 577 564 513 615 695 779 879 949 1041 1145 1264 1388 1544 1644 1804 1879 17 | 675 659 651 639 632 576 695 782 879 957 1046 1141 1254 1391 1524 1614 1793 1871 18 | 739 734 726 719 715 708 663 776 901 971 1022 1117 1232 1355 1478 1616 1766 1887 19 | 819 821 805 784 773 761 736 761 888 958 1044 1145 1237 1346 1487 1614 1721 1891 20 | 899 895 871 852 856 861 854 759 883 984 1027 1113 1226 1355 1467 1568 1703 1885 21 | 973 946 955 925 939 927 902 920 870 987 1061 1145 1234 1319 1439 1552 1722 1859 22 | c 23 | .281 .288 .283 .282 .278 .281 .272 .274 .268 .257 .251 .243 .246 .251 .254 .268 .304 .308 24 | .290 .297 .291 .290 .286 .282 .277 .275 .267 .254 .252 .244 .250 .257 .260 .274 .308 .312 25 | .294 .299 .293 .294 .288 .289 .281 .276 .265 .256 .251 .247 .258 .264 .268 .283 .318 .317 26 | .297 .298 .302 .300 .289 .295 .290 .276 .264 .256 .260 .258 .268 .277 .281 .292 .330 .327 27 | .305 .311 .313 .315 .305 .304 .299 .279 .271 .272 .273 .276 .285 .290 .293 .302 .340 .340 28 | .292 .294 .303 .305 .301 .307 .290 .277 .274 .278 .287 .288 .295 .302 .306 .312 .343 .346 29 | .268 .277 .279 .285 .285 .290 .279 .278 .280 .283 .295 .296 .305 .310 .313 .315 .342 .346 30 | .288 .285 .280 .278 .278 .277 .272 .271 .279 .288 .297 .305 .310 .313 .311 .310 .335 .338 31 | .314 .304 .292 .282 .275 .275 .262 .272 .290 .293 .299 .307 .308 .310 .303 .302 .325 .328 32 | .346 .329 .313 .295 .283 .275 .264 .274 .288 .302 .307 .310 .306 .307 .292 .296 .315 .320 33 | .320 .295 .326 .318 .294 .277 .275 .271 .293 .303 .305 .309 .309 .303 .294 .294 .310 .313 34 | .304 .310 .297 .320 .317 .297 .283 .274 .298 .305 .308 .311 .313 .300 .290 .293 .305 .306 35 | .314 .313 .308 .297 .325 .314 .293 .276 .292 .309 .314 .308 .303 .296 .286 .291 .301 .302 36 | .308 .311 .307 .312 .288 .340 .305 .285 .294 .310 .315 .309 .296 .285 .281 .288 .298 .295 37 | .313 .310 .315 .303 .313 .294 .331 .286 .294 .307 .320 .316 .303 .281 .278 .285 .290 .292 38 | .315 .306 .308 .297 .295 .283 .334 .297 .280 .294 .314 .321 .313 .291 .280 .279 .287 .290 39 | .308 .304 .305 .297 .279 .285 .251 .278 .278 .284 .297 .314 .307 .289 .274 .274 .274 .291 40 | .301 .299 .298 .285 .265 .279 .241 .285 .260 .286 .302 .306 .302 .288 .277 .263 .271 .293 41 | -------------------------------------------------------------------------------- /Source/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: WebKit 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: DontAlign 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: false 10 | AlignTrailingComments: false 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Inline 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: MultiLine 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BreakBeforeBraces: Custom 24 | BraceWrapping: 25 | AfterClass: true 26 | AfterControlStatement: true 27 | AfterEnum: true 28 | AfterFunction: true 29 | AfterNamespace: true 30 | AfterObjCDeclaration: true 31 | AfterStruct: true 32 | AfterUnion: true 33 | AfterExternBlock: true 34 | BeforeCatch: true 35 | BeforeElse: true 36 | IndentBraces: false 37 | SplitEmptyFunction: true 38 | SplitEmptyRecord: false 39 | SplitEmptyNamespace: false 40 | BreakBeforeBinaryOperators: All 41 | BreakBeforeInheritanceComma: false 42 | BreakInheritanceList: BeforeColon 43 | BreakBeforeTernaryOperators: true 44 | BreakConstructorInitializersBeforeComma: false 45 | BreakConstructorInitializers: BeforeColon 46 | BreakAfterJavaFieldAnnotations: false 47 | BreakStringLiterals: true 48 | ColumnLimit: 0 49 | CommentPragmas: '^ IWYU pragma:' 50 | CompactNamespaces: false 51 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 52 | ConstructorInitializerIndentWidth: 4 53 | ContinuationIndentWidth: 4 54 | Cpp11BracedListStyle: false 55 | DerivePointerAlignment: false 56 | DisableFormat: false 57 | ExperimentalAutoDetectBinPacking: false 58 | FixNamespaceComments: false 59 | ForEachMacros: 60 | - foreach 61 | - Q_FOREACH 62 | - BOOST_FOREACH 63 | IncludeBlocks: Preserve 64 | IncludeCategories: 65 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 66 | Priority: 2 67 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 68 | Priority: 3 69 | - Regex: '.*' 70 | Priority: 1 71 | IncludeIsMainRegex: '(Test)?$' 72 | IndentCaseLabels: false 73 | IndentPPDirectives: None 74 | IndentWidth: 4 75 | IndentWrappedFunctionNames: false 76 | JavaScriptQuotes: Leave 77 | JavaScriptWrapImports: true 78 | KeepEmptyLinesAtTheStartOfBlocks: true 79 | MacroBlockBegin: '' 80 | MacroBlockEnd: '' 81 | MaxEmptyLinesToKeep: 1 82 | NamespaceIndentation: Inner 83 | ObjCBinPackProtocolList: Auto 84 | ObjCBlockIndentWidth: 4 85 | ObjCSpaceAfterProperty: true 86 | ObjCSpaceBeforeProtocolList: true 87 | PenaltyBreakAssignment: 2 88 | PenaltyBreakBeforeFirstCallParameter: 19 89 | PenaltyBreakComment: 300 90 | PenaltyBreakFirstLessLess: 120 91 | PenaltyBreakString: 1000 92 | PenaltyBreakTemplateDeclaration: 10 93 | PenaltyExcessCharacter: 1000000 94 | PenaltyReturnTypeOnItsOwnLine: 60 95 | PointerAlignment: Left 96 | ReflowComments: true 97 | SortIncludes: true 98 | SortUsingDeclarations: true 99 | SpaceAfterCStyleCast: false 100 | SpaceAfterTemplateKeyword: true 101 | SpaceBeforeAssignmentOperators: true 102 | SpaceBeforeCpp11BracedList: true 103 | SpaceBeforeCtorInitializerColon: true 104 | SpaceBeforeInheritanceColon: true 105 | SpaceBeforeParens: ControlStatements 106 | SpaceBeforeRangeBasedForLoopColon: true 107 | SpaceInEmptyParentheses: false 108 | SpacesBeforeTrailingComments: 1 109 | SpacesInAngles: false 110 | SpacesInContainerLiterals: true 111 | SpacesInCStyleCastParentheses: false 112 | SpacesInParentheses: false 113 | SpacesInSquareBrackets: false 114 | Standard: Cpp11 115 | StatementMacros: 116 | - Q_UNUSED 117 | - QT_REQUIRE_VERSION 118 | TabWidth: 8 119 | UseTab: Never 120 | ... 121 | 122 | -------------------------------------------------------------------------------- /lightweaver/Data/Barklem_dfdata.dat: -------------------------------------------------------------------------------- 1 | c 2 | c d-f data from Barklem, O'Mara and Ross, 1998, MNRAS, 296, 1057 3 | c 4 | 808 873 958 1059 1175 1306 1453 1615 1793 1979 2121 2203 2461 2604 2764 2757 2784 3156 5 | 798 866 953 1052 1172 1299 1450 1606 1776 1967 2114 2196 2451 2601 2763 2767 2783 3142 6 | 781 848 934 1030 1149 1276 1416 1596 1751 1944 2100 2188 2436 2594 2767 2777 2795 3123 7 | 766 831 915 1010 1124 1239 1398 1564 1729 1912 2083 2180 2426 2585 2776 2790 2808 3106 8 | 750 814 897 987 1097 1201 1355 1530 1718 1875 2060 2171 2414 2575 2779 2809 2820 3103 9 | 733 797 872 950 1049 1166 1326 1502 1670 1851 2026 2165 2396 2562 2779 2827 2832 3099 10 | 726 786 853 936 1011 1128 1303 1472 1649 1844 1979 2159 2371 2548 2778 2840 2848 3103 11 | 709 783 847 912 1002 1093 1270 1419 1606 1787 1951 2139 2335 2533 2775 2847 2863 3104 12 | 758 721 838 907 1010 1066 1211 1401 1600 1774 1972 2098 2313 2528 2781 2857 2892 3121 13 | 869 882 820 870 1003 1098 1165 1368 1527 1735 1896 2030 2288 2534 2776 2844 2902 3123 14 | 970 967 934 938 918 1130 1194 1287 1507 1679 1821 2021 2271 2525 2732 2786 2882 3085 15 | 1079 1043 1056 1007 1014 1021 1200 1326 1424 1668 1818 1988 2242 2493 2672 2719 2853 3035 16 | 1174 1173 1127 1154 1104 1099 1169 1288 1442 1580 1704 1882 2136 2400 2561 2648 2832 2994 17 | 1285 1278 1269 1225 1252 1229 1116 1343 1380 1594 1710 1874 2054 2309 2484 2607 2813 2932 18 | 1440 1408 1422 1380 1383 1341 1361 1192 1448 1454 1675 1873 2069 2246 2432 2610 2811 2878 19 | 1572 1545 1553 1517 1481 1502 1469 1349 1373 1561 1586 1781 2072 2301 2490 2626 2754 2832 20 | 1698 1701 1694 1641 1617 1651 1566 1600 1374 1547 1698 1749 1989 2289 2511 2594 2689 2774 21 | 1870 1841 1786 1752 1777 1757 1666 1732 1522 1533 1707 1817 1928 2194 2435 2574 2665 2742 22 | c 23 | .295 .286 .299 .300 .307 .310 .311 .311 .316 .319 .325 .351 .364 .369 .372 .379 .373 .351 24 | .295 .295 .301 .302 .311 .316 .314 .314 .320 .321 .324 .349 .361 .365 .368 .374 .368 .349 25 | .286 .298 .302 .304 .311 .323 .321 .319 .324 .323 .323 .345 .355 .358 .362 .367 .361 .343 26 | .290 .295 .307 .316 .322 .329 .326 .325 .329 .324 .321 .343 .350 .351 .354 .360 .358 .337 27 | .292 .299 .307 .321 .327 .336 .333 .330 .330 .320 .321 .338 .344 .344 .345 .352 .352 .332 28 | .291 .299 .309 .323 .335 .339 .335 .333 .327 .323 .319 .333 .336 .336 .336 .344 .345 .329 29 | .297 .302 .312 .321 .340 .338 .333 .327 .325 .319 .318 .324 .329 .330 .330 .336 .337 .325 30 | .319 .314 .317 .327 .334 .344 .339 .327 .323 .318 .312 .318 .319 .322 .322 .326 .327 .316 31 | .333 .328 .339 .325 .359 .351 .332 .325 .322 .311 .309 .310 .311 .316 .314 .317 .321 .313 32 | .274 .273 .323 .412 .318 .339 .359 .328 .324 .311 .309 .325 .322 .315 .318 .319 .325 .314 33 | .297 .296 .273 .302 .436 .325 .354 .335 .326 .311 .314 .330 .323 .324 .325 .323 .330 .314 34 | .284 .295 .296 .280 .300 .438 .322 .348 .332 .318 .320 .332 .335 .334 .335 .331 .333 .309 35 | .280 .278 .285 .297 .279 .320 .445 .319 .320 .324 .328 .338 .348 .346 .345 .336 .328 .300 36 | .280 .273 .267 .273 .284 .268 .343 .390 .323 .308 .318 .325 .343 .348 .346 .337 .311 .286 37 | .277 .270 .260 .266 .276 .263 .294 .408 .337 .324 .299 .308 .331 .334 .345 .327 .315 .280 38 | .270 .262 .258 .260 .273 .273 .262 .375 .410 .298 .312 .294 .313 .331 .328 .322 .307 .270 39 | .271 .267 .262 .264 .274 .269 .261 .323 .351 .359 .294 .325 .310 .318 .321 .315 .291 .268 40 | .275 .276 .272 .276 .279 .270 .264 .295 .393 .340 .319 .287 .320 .330 .316 .302 .280 .261 41 | 42 | -------------------------------------------------------------------------------- /examples/plot_JudgeDynamicValidation.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ====================== 3 | Time-dependent Example 4 | ====================== 5 | Simple illustrative example of time-dependent method. Herein we reproduce the 6 | time-dependent population figure present in Judge 2017. Here the complete 7 | Rybicki-Hummer MALI method is used. 8 | Herein we also conserve charge. 9 | 10 | Judge (2017): ApJ 851, 5 11 | ''' 12 | from lightweaver.fal import Falc82 13 | from lightweaver.rh_atoms import H_4_atom, C_atom, O_atom, Si_atom, Al_atom, CaII_atom, Fe_atom, He_atom, MgII_atom, N_atom, Na_atom, S_atom 14 | import matplotlib.pyplot as plt 15 | import time 16 | import numpy as np 17 | import lightweaver as lw 18 | 19 | #%% 20 | # Set up the standard FAL C 82 point initial atmosphere. 21 | atmos = Falc82() 22 | atmos.quadrature(5) 23 | aSet = lw.RadiativeSet([H_4_atom(), C_atom(), O_atom(), Si_atom(), Al_atom(), CaII_atom(),\ 24 | Fe_atom(), He_atom(), MgII_atom(), N_atom(), Na_atom(), S_atom()]) 25 | aSet.set_active('H') 26 | spect = aSet.compute_wavelength_grid() 27 | 28 | eqPops = aSet.iterate_lte_ne_eq_pops(atmos) 29 | ctx = lw.Context(atmos, spect, eqPops, conserveCharge=True, Nthreads=1) 30 | 31 | #%% 32 | # Find the initial statistical equilibrium solution, 33 | lw.iterate_ctx_se(ctx) 34 | 35 | print('Achieved initial Stat Eq\n\n') 36 | 37 | 38 | #%% 39 | # Simulation parameters, timestep, number of steps to run for, and how many 40 | # times to attempt to solve the equations for convergence per step. 41 | start = time.time() 42 | dt = 0.1 43 | NtStep = 30 44 | NsubStep = 100 45 | 46 | #%% 47 | # Perturb the atmospheric temperature structure like in the paper. 48 | prevT = np.copy(atmos.temperature) 49 | for i in range(11, 31): 50 | di = (i - 20.0) / 3.0 51 | atmos.temperature[i] *= 1.0 + 2.0 * np.exp(-di**2) 52 | 53 | #%% 54 | # Solve the problem 55 | hPops = [np.copy(eqPops['H'])] 56 | subIters = [] 57 | for it in range(NtStep): 58 | # Recompute line profiles etc to account for changing electron density and temperature. 59 | ctx.update_deps() 60 | 61 | prevState = None 62 | for sub in range(NsubStep): 63 | JUpdate = ctx.formal_sol_gamma_matrices() 64 | # If prevState is None, then the function assumes that this is the 65 | # subiteration for this step and constructs and returns prevState 66 | popsUpdate, prevState = ctx.time_dep_update(dt, prevState) 67 | # Update electron density. 68 | # If conserveCharge is set to True when the context is constructed, then 69 | # the effects of `time_dep_update` are included in the IterationUpdate 70 | # returned from `nr_post_update`, as the Context is expecting this to be 71 | # called immediately after `time_dep_update`. 72 | nrUpdate = ctx.nr_post_update(timeDependentData={'dt': dt, 'nPrev': prevState}) 73 | 74 | # Check subiteration convergence 75 | if nrUpdate.dPopsMax < 1e-3 and JUpdate.dJMax < 3e-3: 76 | subIters.append(sub) 77 | break 78 | else: 79 | raise ValueError('No convergence within required Nsubstep') 80 | 81 | hPops.append(np.copy(eqPops['H'])) 82 | print('Iteration %d (%f s) done after %d sub iterations' % (it, (it+1)*dt, sub)) 83 | 84 | # input() 85 | end = time.time() 86 | 87 | #%% 88 | # Reproduce Judge plot. 89 | 90 | initialAtmos = Falc82() 91 | 92 | plt.ion() 93 | fig, ax = plt.subplots(2,2, sharex=True) 94 | ax = ax.flatten() 95 | cmass = np.log10(atmos.cmass/1e1) 96 | 97 | ax[0].plot(cmass, initialAtmos.temperature, 'k') 98 | ax[0].plot(cmass, atmos.temperature, '--') 99 | 100 | for p in hPops[1:]: 101 | ax[1].plot(cmass, np.log10(p[0,:]/1e6)) 102 | ax[2].plot(cmass, np.log10(p[1,:]/1e6)) 103 | ax[3].plot(cmass, np.log10(p[-1,:]/1e6)) 104 | 105 | p = hPops[0] 106 | ax[1].plot(cmass, np.log10(p[0,:]/1e6), 'k') 107 | ax[2].plot(cmass, np.log10(p[1,:]/1e6), 'k') 108 | ax[3].plot(cmass, np.log10(p[-1,:]/1e6), 'k') 109 | 110 | ax[0].set_xlim(-4.935, -4.931) 111 | ax[0].set_ylim(0, 6e4) 112 | ax[1].set_ylim(6, 11) 113 | ax[2].set_ylim(1, 6) 114 | ax[3].set_ylim(10, 11) 115 | 116 | print('Time taken: %.2f s' % (end-start)) 117 | -------------------------------------------------------------------------------- /Source/Bezier.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_BEZIER_HPP 2 | #define CMO_BEZIER_HPP 3 | 4 | #include "Constants.hpp" 5 | #include "Simd.hpp" 6 | #include 7 | 8 | 9 | namespace Bezier 10 | { 11 | #if defined(__STDC_IEC_559__) || (defined(_WIN32) && defined(_M_AMD64)) 12 | // NOTE(cmo): This checks to ensure we have a valid IEE754 environment~ish 13 | inline ForceInline f64 my_copysign(f64 a, f64 b) 14 | { 15 | // NOTE(cmo): Copy sign of b onto a. 16 | // The standard library one doesn't seem to inline in Windows 17 | union Pun 18 | { 19 | double f; 20 | uint64_t i; 21 | }; 22 | Pun pa, pb; 23 | pa.f = abs(a); 24 | pb.f = b; 25 | 26 | pa.i |= pb.i & 1ULL << 63; // or with sign bit of b 27 | return pa.f; 28 | } 29 | #else 30 | inline f64 my_copysign(f64 a, f64 b) 31 | { 32 | return std::copysign(a, b); 33 | } 34 | #endif 35 | 36 | #ifdef USE_FRITSCH_DERIVATIVE 37 | inline f64 cent_deriv(f64 dsup, f64 dsdn, f64 chiup, f64 chic, f64 chidn) 38 | { 39 | /* --- Derivative Fritsch & Butland (1984) --- */ 40 | 41 | double fim1, fi, alpha, wprime; 42 | 43 | fim1 = (chic - chiup) / dsup; 44 | fi = (chidn - chic) / dsdn; 45 | 46 | if (fim1 * fi > 0.0) 47 | { 48 | alpha = 1.0 / 3.0 * (1.0 + dsdn / (dsdn + dsup)); 49 | wprime = (fim1 * fi) / ((1.0 - alpha) * fim1 + alpha * fi); 50 | } 51 | else 52 | { 53 | wprime = 0.0; 54 | } 55 | return wprime; 56 | } 57 | #else 58 | inline ForceInline f64 cent_deriv(f64 dsuw, f64 dsdw, f64 yuw, f64 y0, f64 ydw) 59 | { 60 | // Steffen (1990) derivatives 61 | const f64 S0 = (ydw - y0) / dsdw; 62 | const f64 Suw = (y0 - yuw) / dsuw; 63 | const f64 P0 = abs((Suw * dsdw + S0 * dsuw) / (dsdw + dsuw)); 64 | return (my_copysign(1.0, S0) + my_copysign(1.0, Suw)) * min(abs(Suw), min(abs(S0), 0.5 * P0)); 65 | } 66 | #endif 67 | 68 | inline void cent_deriv(F64View2D& wprime, f64 dsup, f64 dsdn, const F64View2D& chiup, const F64View2D& chic, const F64View2D& chidn) 69 | { 70 | for (int j = 0; j < 4; j++) 71 | for (int i = 0; i < 4; i++) 72 | wprime(j, i) = cent_deriv(dsup, dsdn, chiup(j, i), chic(j, i), chidn(j, i)); 73 | } 74 | 75 | inline void cent_deriv(F64View& wprime, f64 dsup, f64 dsdn, const F64View& chiup, const F64View& chic, const F64View& chidn) 76 | { 77 | for (int i = 0; i < 4; i++) 78 | wprime(i) = cent_deriv(dsup, dsdn, chiup(i), chic(i), chidn(i)); 79 | } 80 | 81 | inline ForceInline void Bezier3_coeffs(f64 dt, f64* alpha, f64* beta, f64* gamma, f64* delta, f64* edt) 82 | { 83 | /* --- 84 | 85 | Integration coeffs. for cubic Bezier interpolants 86 | Use Taylor expansion if dtau is small 87 | 88 | Coefficients go with: 89 | alpha: Suw 90 | beta: S0 91 | gamma: Cuw 92 | delta: C0 93 | edt: exp(-dtau) 94 | 95 | --- */ 96 | 97 | f64 dt2 = square(dt); 98 | f64 dt3 = dt2 * dt; 99 | 100 | if (dt < 5e-2) 101 | { 102 | *edt = 1.0 - dt + 0.5 * dt2 - dt3 / 6.0; 103 | // NOTE(cmo): To get the correct taylor expansion need to take terms up 104 | // to dt^6 due to the 1/dt^3 term. Then truncate at dt^3. 105 | *alpha = 0.25 * dt - 0.2 * dt2 + dt3 / 12.0; 106 | *beta = 0.25 * dt - 0.05 * dt2 + dt3 / 120.0; 107 | *gamma = 0.25 * dt - 0.15 * dt2 + 0.05 * dt3; 108 | *delta = 0.25 * dt - 0.1 * dt2 + 0.025 * dt3; 109 | } 110 | else if (dt > 30.0) 111 | { 112 | *edt = 0.0; 113 | *alpha = 6.0 / dt3; 114 | *beta = (-6.0 + 6.0 * dt - 3.0 * dt2 + dt3) / dt3; 115 | *gamma = 3.0 * (2.0 * dt - 6.0) / dt3; 116 | *delta = 3.0 * (6.0 - 4.0 * dt + dt2) / dt3; 117 | } 118 | else 119 | { 120 | *edt = exp(-dt); 121 | 122 | *alpha = (6.0 - *edt * (6.0 + 6.0 * dt + 3 * dt2 + dt3)) / dt3; 123 | *beta = (6.0 * *edt - 6.0 + 6.0 * dt - 3.0 * dt2 + dt3) / dt3; 124 | *gamma = 3.0 * (2.0 * dt - 6.0 + *edt * (6.0 + 4.0 * dt + dt2)) / dt3; 125 | *delta = 3.0 * (6.0 - 4.0 * dt + dt2 - 2.0 * *edt * (3.0 + dt)) / dt3; 126 | } 127 | } 128 | 129 | constexpr bool LimitControlPoints = false; 130 | constexpr f64 limit_control_point(f64 c) 131 | { 132 | if (LimitControlPoints) 133 | return max(c, 0.0); 134 | else 135 | return c; 136 | } 137 | } 138 | 139 | #else 140 | #endif -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Lightweaver documentation master file, created by 2 | sphinx-quickstart on Fri Sep 25 16:56:45 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Lightweaver's documentation! 7 | ======================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | auto_examples/index 14 | lightweaver-api 15 | 16 | Introduction 17 | ------------ 18 | 19 | Lightweaver is a Non-LTE (NLTE) radiative transfer framework for constructing 20 | simulations of optically thick spectral lines (primarily solar). It is 21 | available under the `MIT license`_. The flexible nature of its design allows 22 | it to be simply used in direct line synthesis or as a component of more 23 | involved frameworks (such as radiation hydrodynamics or inversion packages). 24 | Currently, one dimensional plane-parallel, and two-dimensional Cartesian 25 | atmospheres are supported, but three-dimensional setups could be added simply 26 | with a specific formal solver. It is well tested against RH_ and SNAPI_. 27 | 28 | .. _MIT license: https://opensource.org/licenses/MIT 29 | .. _RH: https://github.com/ITA-Solar/rh 30 | .. _SNAPI: https://github.com/ivanzmilic/snapi 31 | 32 | The framework is managed from python (3.8+) whilst calling into an optimised 33 | C++ backend. Whilst following a similar methods, Lightweaver is often 34 | considerably faster than RH on non-trivial problems, whilst being slightly 35 | slower on trivial single-threaded problems due to the increased 36 | initialisation time the python layer brings with it. 37 | The backend can parallelise directly over as many threads as desired on 38 | single address space systems. MPI parallelisation across nodes can be 39 | accomplished manually on top of the framework using the packages available in 40 | python. 41 | 42 | Whilst the core numerics are implented in C++, as much of the non-performance 43 | critical code as possible is implemented in Python, and the code currently 44 | only has a Python interface (provided through a Cython binding module). These 45 | bindings could be rewritten in another language, so long as the same 46 | information can be provided to the C++ core. 47 | 48 | The aim of Lightweaver is to provide an NLTE Framework, rather than a "code". 49 | That is to say, it should be more maleable, and provide easier access to 50 | experimentation, with most forms of experimentation (unless one wants to play 51 | with formal solvers or iteration schemes), being available directly from 52 | python. Formal solvers that comply with the interface defined in Lightweaver 53 | can be compiled into separate shared libraries and then loaded at runtime. 54 | The preceding concepts are inspired by the well-recieved machine learning 55 | frameworks such as PyTorch and Tensorflow. 56 | 57 | Installation 58 | ------------ 59 | 60 | The most recent release of Lightweaver will always be available on PyPI and 61 | is pre-compiled for Linux, Windows, and macOS (intel). 62 | 63 | Lightweaver requires python 3.10+, and it is recommended to be run inside a virtual environment using ``conda``. 64 | 65 | In this case a new virtual environment can be created with: 66 | 67 | .. code-block:: bash 68 | 69 | conda create -n Lightweaver python=3.12 70 | 71 | then, activate the environment: 72 | 73 | .. code-block:: bash 74 | 75 | conda activate Lightweaver 76 | 77 | and Lightweaver can then be installed with 78 | 79 | .. code-block:: bash 80 | 81 | python -m pip install lightweaver 82 | 83 | On other platforms you will need to compile the library, which will require a 84 | C++17 compiler on your path, at which point setuptools should handle 85 | everything. After downloading the release, or mainline version that you wish 86 | to install, you should be able to 87 | 88 | .. code-block:: bash 89 | 90 | python -m pip install . 91 | 92 | from the Lightweaver directory created. You may also wish to do this to 93 | create a more optimised version for modern machines; the PyPI versions 94 | support Sandy Bridge CPUs, so newer machines may have wider instruction sets 95 | available. 96 | For doing a "development" installation for working inplace on the project, 97 | you may with to use ``python -m pip install -e .``. 98 | 99 | Indices and tables 100 | ================== 101 | 102 | * :ref:`genindex` 103 | * :ref:`modindex` 104 | * :ref:`search` 105 | -------------------------------------------------------------------------------- /Source/LwInternal.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LW_INTERNAL_HPP 2 | #define CMO_LW_INTERNAL_HPP 3 | #include "Constants.hpp" 4 | #include "CmoArray.hpp" 5 | #include "LwFormalInterface.hpp" 6 | #include 7 | 8 | struct Atmosphere; 9 | struct Spectrum; 10 | struct Background; 11 | struct Atom; 12 | struct DepthData; 13 | 14 | namespace LwInternal 15 | { 16 | struct FormalData 17 | { 18 | int width; 19 | Atmosphere* atmos; 20 | F64View chi; 21 | F64View S; 22 | F64View I; 23 | F64View Psi; 24 | Interp2d interp; 25 | 26 | FormalData() : width(1), 27 | atmos(nullptr), 28 | chi(), 29 | S(), 30 | I(), 31 | Psi(), 32 | interp() 33 | {} 34 | }; 35 | 36 | struct FormalDataStokes 37 | { 38 | Atmosphere* atmos; 39 | F64View2D chi; 40 | F64View2D S; 41 | F64View2D I; 42 | FormalData fdIntens; 43 | }; 44 | 45 | struct IntensityCoreData 46 | { 47 | LwFsFn formal_solver; 48 | Atmosphere* atmos; 49 | Spectrum* spect; 50 | FormalData* fd; 51 | Background* background; 52 | DepthData* depthData; 53 | std::vector* activeAtoms; 54 | std::vector* detailedAtoms; 55 | F64Arr* JDag; 56 | F64View chiTot; 57 | F64View etaTot; 58 | F64View Uji; 59 | F64View Vij; 60 | F64View Vji; 61 | F64View I; 62 | F64View S; 63 | F64View Ieff; 64 | F64View PsiStar; 65 | F64View2D JRest; 66 | 67 | IntensityCoreData() = default; 68 | }; 69 | 70 | struct StokesCoreData 71 | { 72 | Atmosphere* atmos; 73 | Spectrum* spect; 74 | FormalDataStokes* fd; 75 | Background* background; 76 | std::vector* activeAtoms; 77 | std::vector* detailedAtoms; 78 | F64Arr* JDag; 79 | F64View2D chiTot; 80 | F64View2D etaTot; 81 | F64View Uji; 82 | F64View Vij; 83 | F64View Vji; 84 | F64View2D I; 85 | F64View2D S; 86 | F64View2D J20; 87 | F64Arr* J20Dag; 88 | }; 89 | 90 | inline void w2(f64 dtau, f64* w) 91 | { 92 | constexpr f64 third = 1.0 / 3.0; 93 | f64 expdt; 94 | 95 | if (dtau < 5.0E-4) 96 | { 97 | w[0] = dtau * (1.0 - 0.5 * dtau); 98 | w[1] = square(dtau) * (0.5 - dtau * third); 99 | } 100 | else if (dtau > 50.0) 101 | { 102 | w[1] = w[0] = 1.0; 103 | } 104 | else 105 | { 106 | expdt = exp(-dtau); 107 | w[0] = 1.0 - expdt; 108 | w[1] = w[0] - dtau * expdt; 109 | } 110 | } 111 | 112 | enum FsMode : u32 113 | { 114 | FsOnly = 0, 115 | UpdateJ = 1 << 0, 116 | UpdateRates = 1 << 1, 117 | PrdOnly = 1 << 2, 118 | PureLambdaIteration = 1 << 3, 119 | UpOnly = 1 << 4, 120 | }; 121 | constexpr inline FsMode 122 | operator|(FsMode a, FsMode b) 123 | { 124 | return static_cast(static_cast(a) | static_cast(b)); 125 | } 126 | 127 | void piecewise_linear_1d(FormalData* fd, int la, int mu, bool toObs, 128 | const F64View1D& wave); 129 | void piecewise_besser_1d(FormalData* fd, int la, int mu, bool toObs, 130 | const F64View1D& wave); 131 | void piecewise_bezier3_1d(FormalData* fd, int la, int mu, bool toObs, 132 | const F64View1D& wave); 133 | void piecewise_linear_2d(FormalData* fd, int la, int mu, bool toObs, 134 | const F64View1D& wave); 135 | void piecewise_besser_2d(FormalData* fd, int la, int mu, bool toObs, 136 | const F64View1D& wave); 137 | void piecewise_parabolic_2d(FormalData* fd, int la, int mu, bool toObs, f64 wav); 138 | void piecewise_stokes_bezier3_1d(FormalDataStokes* fd, int la, int mu, bool toObs, f64 wav, bool polarisedFrequency); 139 | f64 interp_linear_2d(const IntersectionData&, const IntersectionResult&, const F64View2D&); 140 | f64 interp_besser_2d(const IntersectionData&, const IntersectionResult&, const F64View2D&); 141 | } 142 | #else 143 | #endif -------------------------------------------------------------------------------- /lightweaver/benchmark.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import numpy as np 4 | from tqdm import tqdm 5 | from weno4 import weno4 6 | 7 | from lightweaver.config import update_config_file 8 | from .atmosphere import Atmosphere, ScaleType 9 | from .atomic_set import RadiativeSet 10 | from .config import params as rcParams 11 | from .config import get_home_config_path, update_config_file 12 | from .fal import Falc82 13 | from .rh_atoms import CaII_atom, H_6_atom 14 | from .simd_management import get_available_simd_suffixes 15 | from .LwCompiled import LwContext 16 | 17 | __all__ = ['benchmark'] 18 | 19 | def configure_context(Nspace=500, fsIterScheme=None): 20 | ''' 21 | Configure a FALC context (with more or fewer depth points), 1 thread and a 22 | particular iteration scheme. For use in benchmarking. 23 | 24 | Parameters 25 | ---------- 26 | Nspace : int, optional 27 | Number of spatial points to interpolate the atmosphere to. (Default: 500) 28 | fsIterScheme : str, optional 29 | The fsIterScheme to use in the Context. (Default: None, i.e. read from 30 | the user's config) 31 | ''' 32 | fal = Falc82() 33 | interp = lambda x: weno4(np.linspace(0,1,Nspace), np.linspace(0,1,fal.Nspace), x) 34 | 35 | atmos = Atmosphere.make_1d(ScaleType.Geometric, interp(fal.height), 36 | temperature=interp(fal.temperature), vlos=interp(fal.vlos),\ 37 | vturb=interp(fal.vturb), ne=interp(fal.ne), 38 | nHTot=interp(fal.nHTot)) 39 | atmos.quadrature(5) 40 | aSet = RadiativeSet([H_6_atom(), CaII_atom()]) 41 | aSet.set_active('H', 'Ca') 42 | eqPops = aSet.compute_eq_pops(atmos) 43 | spect = aSet.compute_wavelength_grid() 44 | ctx = LwContext(atmos, spect, eqPops, fsIterScheme=fsIterScheme) 45 | return ctx 46 | 47 | def benchmark(Niter=50, Nrep=3, verbose=True, writeConfig=True, warmUp=True): 48 | ''' 49 | Benchmark the various SIMD implementations for Lightweaver's formal solver 50 | and iteration functions. 51 | 52 | Parameters 53 | ---------- 54 | Niter : int, optional 55 | The number of iterations to use for each scheme. (Default: 50) 56 | Nrep : int, optional 57 | The number of repetitions to average for each scheme. (Default: 3) 58 | verbose : bool, optional 59 | Whether to print information as the function runs. (Default: True) 60 | writeConfig : bool, optional 61 | Whether to writ the optimal method to the user's config file. (Default: 62 | True) 63 | warmUp : bool, optional 64 | Whether to run a Context first (discarded) to ensure that all numba jit 65 | code is jitted and warm. (Default: True) 66 | ''' 67 | timer = time.perf_counter 68 | 69 | if verbose: 70 | print('This will take a couple of minutes...') 71 | 72 | suffixes = get_available_simd_suffixes() 73 | suffixes = ['scalar'] + suffixes 74 | methods = [f'mali_full_precond_{suffix}' for suffix in suffixes] 75 | 76 | if warmUp: 77 | ctx = configure_context(fsIterScheme=methods[0]) 78 | for _ in range(max(Niter // 5, 10)): 79 | ctx.formal_sol_gamma_matrices(printUpdate=False) 80 | 81 | timings = [0.0] * len(suffixes) 82 | it = tqdm(methods * Nrep) if verbose else methods * Nrep 83 | for idx, method in enumerate(it): 84 | ctx = configure_context(fsIterScheme=method) 85 | start = timer() 86 | for _ in range(Niter): 87 | ctx.formal_sol_gamma_matrices(printUpdate=False) 88 | end = timer() 89 | duration = (end - start) 90 | timings[idx % len(methods)] += duration 91 | 92 | timings = [t / Nrep for t in timings] 93 | if verbose: 94 | for idx, method in enumerate(methods): 95 | print(f'Timing for method "{method}": {timings[idx]:.3f} s ' 96 | f'({Niter} iterations, {Nrep} repetitions)') 97 | 98 | if writeConfig: 99 | minTiming = min(timings) 100 | minIdx = timings.index(minTiming) 101 | if verbose: 102 | print(f'Selecting method: {methods[minIdx]}') 103 | 104 | impl = suffixes[minIdx] 105 | rcParams['SimdImpl'] = impl 106 | 107 | path = get_home_config_path() 108 | if verbose: 109 | print(f'Writing config to \'{path}\'...') 110 | update_config_file(path) 111 | 112 | if verbose: 113 | print('Benchmark complete.') 114 | -------------------------------------------------------------------------------- /Source/LwAtom.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LW_ATOM_HPP 2 | #define CMO_LW_ATOM_HPP 3 | 4 | #include "CmoArray.hpp" 5 | #include "Ng.hpp" 6 | #include "LwAtmosphere.hpp" 7 | #include "LwMisc.hpp" 8 | #include "LwTransition.hpp" 9 | 10 | #include 11 | 12 | namespace LwInternal 13 | { 14 | struct AtomScratchStore 15 | { 16 | F64Arr2D wla; 17 | F64Arr2D gij; 18 | F64Arr1D eta; 19 | F64Arr2D U; 20 | F64Arr2D chi; 21 | 22 | AtomScratchStore() = default; 23 | AtomScratchStore(int Nlevel, int Ntrans, i64 Nspace, bool detailed, 24 | bool wlaGijStorage, bool defaultPerAtomStorage) 25 | { 26 | if (Ntrans > 0 && wlaGijStorage) 27 | { 28 | wla = F64Arr2D(Ntrans, Nspace); 29 | gij = F64Arr2D(Ntrans, Nspace); 30 | } 31 | 32 | if ((!detailed) && (defaultPerAtomStorage)) 33 | { 34 | eta = F64Arr1D(Nspace); 35 | U = F64Arr2D(Nlevel, Nspace); 36 | chi = F64Arr2D(Nlevel, Nspace); 37 | } 38 | } 39 | }; 40 | } 41 | 42 | struct Atom 43 | { 44 | int Nlevel; 45 | int Ntrans; 46 | 47 | Atmosphere* atmos; 48 | F64View2D n; 49 | F64View2D nStar; 50 | F64View1D nTotal; 51 | F64View1D vBroad; 52 | F64View1D stages; 53 | 54 | F64View3D Gamma; 55 | F64View3D C; 56 | 57 | F64View2D wla; 58 | F64View2D gij; 59 | F64View1D eta; 60 | F64View2D U; 61 | F64View2D chi; 62 | 63 | std::vector trans; 64 | 65 | Ng ng; 66 | LwInternal::AtomScratchStore scratch; 67 | void* methodScratch; 68 | 69 | void init_scratch(i64 Nspace, bool detailed, 70 | bool wlaGijStorage, bool defaultPerAtomStorage) 71 | { 72 | scratch = LwInternal::AtomScratchStore(Nlevel, Ntrans, Nspace, 73 | detailed, wlaGijStorage, 74 | defaultPerAtomStorage); 75 | wla = scratch.wla; 76 | gij = scratch.gij; 77 | eta = scratch.eta; 78 | U = scratch.U; 79 | chi = scratch.chi; 80 | } 81 | 82 | inline void setup_wavelength(int laIdx, int fsWidth=1) 83 | { 84 | namespace C = Constants; 85 | constexpr f64 pi4_h = 4.0 * C::Pi / C::HPlanck; 86 | constexpr f64 hc_4pi = 0.25 * C::HC / C::Pi; 87 | constexpr f64 pi4_hc = 1.0 / hc_4pi; 88 | constexpr f64 hc_k = C::HC / (C::KBoltzmann * C::NM_TO_M); 89 | for (int kr = 0; kr < Ntrans; ++kr) 90 | { 91 | auto& t = *trans[kr]; 92 | if (!t.active(laIdx)) 93 | continue; 94 | 95 | auto g = gij(kr); 96 | auto w = wla(kr); 97 | const int lt = t.lt_idx(laIdx); 98 | auto wlambda = t.wlambda(lt); 99 | if (t.type == TransitionType::LINE) 100 | { 101 | for (int k = 0; k < g.shape(0); ++k) 102 | { 103 | g(k) = t.Bji / t.Bij; 104 | w(k) = wlambda * t.wphi(k) * pi4_hc; 105 | } 106 | } 107 | else 108 | { 109 | const f64 hc_kl = hc_k / t.wavelength(t.lt_idx(laIdx)); 110 | const f64 wlambda_lambda = wlambda / t.wavelength(t.lt_idx(laIdx)); 111 | for (int k = 0; k < g.shape(0); ++k) 112 | { 113 | g(k) = nStar(t.i, k) / nStar(t.j, k) * exp(-hc_kl / atmos->temperature(k)); 114 | w(k) = wlambda_lambda * pi4_h; 115 | } 116 | } 117 | 118 | // NOTE(cmo): We have to do a linear interpolation on rhoPrd in the 119 | // case of hybrid PRD, so we can't pre-multiply here in that 120 | // instance. 121 | if (t.rhoPrd && !t.hPrdCoeffs) 122 | for (int k = 0; k < g.shape(0); ++k) 123 | g(k) *= t.rhoPrd(lt, k); 124 | 125 | if (!t.gij) 126 | t.gij = g; 127 | } 128 | } 129 | 130 | inline void zero_angle_dependent_vars() 131 | { 132 | eta.fill(0.0); 133 | U.fill(0.0); 134 | chi.fill(0.0); 135 | } 136 | 137 | inline void zero_rates() 138 | { 139 | for (auto& t : trans) 140 | t->zero_rates(); 141 | } 142 | 143 | inline void zero_Gamma() 144 | { 145 | if (!Gamma) 146 | return; 147 | 148 | Gamma.fill(0.0); 149 | } 150 | }; 151 | 152 | #else 153 | #endif -------------------------------------------------------------------------------- /lightweaver/Data/Barklem_spdata.dat: -------------------------------------------------------------------------------- 1 | c 2 | c This is the s-p data from Anstee and O'Mara 1995, MNRAS 276,859 3 | c 4 | 126 140 165 202 247 299 346 383 435 491 553 617 685 769 838 925 1011 1082 5 | 140 150 162 183 218 273 327 385 440 501 557 620 701 764 838 923 1025 1085 6 | 154 167 175 192 216 251 299 357 423 487 549 617 684 759 834 910 1014 1064 7 | 166 180 192 206 226 253 291 339 397 459 532 600 676 755 832 896 1002 1055 8 | 208 194 207 223 242 265 296 335 384 445 511 583 656 726 817 889 988 1044 9 | 262 254 220 239 261 283 310 344 388 442 496 568 635 725 791 890 970 1036 10 | 311 306 299 251 280 304 330 361 396 443 500 563 630 704 796 880 951 1033 11 | 358 359 350 338 293 323 352 381 416 455 511 566 635 706 780 859 946 1039 12 | 411 409 405 392 370 340 375 406 439 478 525 580 644 714 790 873 961 1050 13 | 462 463 459 450 443 400 394 432 467 501 546 595 650 711 786 873 963 1050 14 | 522 525 529 524 516 518 438 454 495 532 565 621 671 741 813 874 951 1034 15 | 589 593 590 583 579 568 565 483 517 560 600 644 691 752 821 904 978 1048 16 | 658 655 666 657 649 653 649 587 549 592 674 674 728 782 833 902 992 1084 17 | 738 742 747 725 721 729 699 730 626 622 668 721 765 809 887 938 1001 1109 18 | 838 838 810 809 790 800 769 815 757 679 704 755 806 854 901 974 1034 1105 19 | 942 946 925 901 918 895 919 897 933 890 785 797 859 908 976 1020 1115 1173 20 | 1059 1061 1056 1061 1074 1031 1036 1036 993 1038 932 852 878 943 1003 1074 1131 1200 21 | 1069 1076 1083 1095 1102 1091 1126 1156 1103 1149 1157 1036 972 1007 1064 1124 1209 1283 22 | 1338 1350 1356 1354 1324 1301 1312 1318 1257 1239 1297 1233 1089 1059 1106 1180 1218 1317 23 | 1409 1398 1367 1336 1313 1313 1409 1354 1317 1287 1353 1386 1279 1158 1141 1188 1260 1335 24 | 1328 1332 1342 1369 1405 1451 1502 1524 1506 1477 1522 1594 1572 1436 1328 1325 1382 1446 25 | c 26 | .268 .269 .335 .377 .327 .286 .273 .270 .271 .268 .267 .264 .264 .264 .261 .256 .248 .245 27 | .261 .256 .254 .282 .327 .355 .321 .293 .287 .271 .267 .273 .270 .270 .268 .268 .264 .263 28 | .266 .264 .257 .252 .267 .289 .325 .339 .319 .301 .292 .284 .281 .281 .277 .282 .276 .274 29 | .262 .274 .258 .251 .247 .254 .273 .291 .316 .322 .320 .302 .294 .290 .287 .292 .283 .277 30 | .322 .275 .264 .259 .250 .245 .273 .255 .271 .284 .294 .308 .296 .299 .288 .289 .282 .278 31 | .267 .300 .260 .268 .254 .242 .243 .242 .239 .246 .267 .277 .280 .290 .282 .281 .274 .271 32 | .259 .274 .275 .252 .265 .248 .249 .237 .283 .236 .247 .254 .254 .271 .268 .267 .258 .262 33 | .260 .255 .268 .268 .268 .264 .248 .239 .229 .240 .236 .234 .238 .244 .252 .251 .244 .255 34 | .255 .255 .244 .247 .317 .246 .255 .244 .237 .231 .227 .231 .235 .232 .235 .241 .237 .245 35 | .256 .254 .254 .249 .227 .319 .253 .253 .240 .237 .238 .233 .231 .230 .228 .234 .227 .241 36 | .257 .254 .252 .235 .253 .240 .284 .251 .246 .241 .235 .228 .222 .225 .225 .219 .228 .233 37 | .244 .240 .245 .238 .248 .230 .283 .252 .244 .244 .238 .235 .234 .236 .228 .224 .225 .231 38 | .244 .241 .244 .237 .237 .249 .219 .324 .239 .245 .242 .242 .232 .233 .221 .227 .231 .218 39 | .241 .245 .249 .239 .243 .250 .217 .254 .308 .237 .247 .244 .234 .228 .233 .224 .227 .226 40 | .243 .243 .232 .227 .235 .253 .227 .220 .320 .270 .243 .252 .248 .238 .234 .241 .225 .227 41 | .225 .226 .234 .230 .226 .233 .249 .225 .216 .300 .286 .237 .240 .247 .243 .234 .231 .238 42 | .268 .260 .247 .238 .233 .241 .254 .248 .207 .227 .315 .260 .226 .237 .240 .239 .239 .240 43 | .248 .246 .238 .226 .213 .221 .226 .226 .204 .194 .248 .316 .234 .216 .236 .233 .221 .230 44 | .200 .202 .198 .194 .206 .207 .227 .224 .207 .185 .198 .275 .315 .233 .229 .231 .233 .236 45 | .202 .209 .221 .226 .230 .245 .202 .257 .246 .225 .215 .246 .320 .321 .244 .239 .251 .253 46 | .246 .248 .255 .265 .274 .285 .292 .284 .273 .250 .225 .239 .295 .352 .320 .258 .260 .269 47 | -------------------------------------------------------------------------------- /Utils/J_A+A_636_A24/ReadMe: -------------------------------------------------------------------------------- 1 | J/A+A/636/A24 Polarised radiative transfer angular quadratures (Stepan+, 2020) 2 | ================================================================================ 3 | Near optimal angular quadratures for polarised radiative transfer. 4 | Stepan J., Jaume Bestard J., Trujillo Bueno J. 5 | 6 | =2020A&A...636A..24S (SIMBAD/NED BibCode) 7 | ================================================================================ 8 | ADC_Keywords: Polarization 9 | Keywords: methods: numerical - polarisation - radiative transfer 10 | 11 | Abstract: 12 | In three-dimensional (3D) radiative transfer (RT) problems, the tensor 13 | product quadratures are generally not optimal in terms of the number 14 | of discrete ray directions needed for a given accuracy of the angular 15 | integration of the radiation field. In this paper, we derive a new set 16 | of angular quadrature rules that are more suitable for solving 3D RT 17 | problems with the short- and long-characteristics formal solvers. 18 | These quadratures are more suitable than the currently used ones for 19 | the numerical calculation of the radiation field tensors that are 20 | relevant in the problem of the generation and transfer of polarised 21 | radiation without assuming local thermodynamical equilibrium 22 | (non-LTE). We show that our new quadratures can save up to about 30% 23 | of computing time with respect to the Gaussian-trapezoidal product 24 | 25 | Description: 26 | Quadrature for unpolarised and polarised radiations. 27 | 28 | File Summary: 29 | -------------------------------------------------------------------------------- 30 | FileName Lrecl Records Explanations 31 | -------------------------------------------------------------------------------- 32 | ReadMe 80 . This file 33 | il3n1.dat 47 1 *Quadrature for unpolarised radiation, 34 | Unpolarised case for L=3 and n=1 35 | il7n3.dat 64 3 *Quadrature for unpolarised radiation, 36 | Unpolarised case for L=7 and n=3 37 | il9n6.dat 64 6 *Quadrature for unpolarised radiation, 38 | Unpolarised case for L=9 and n=6 39 | il11n7.dat 64 7 *Quadrature for unpolarised radiation, 40 | Unpolarised case for L=11 and n=7 41 | il13n10.dat 64 10 *Quadrature for unpolarised radiation, 42 | Unpolarised case for L=13 and n=10 43 | il15n11.dat 64 11 *Quadrature for unpolarised radiation, 44 | Unpolarised case for L=15 and n=11 45 | iql3n3.dat 64 3 *Quadrature for polarised radiation, 46 | Polarised case for L=3 and n=3 47 | iql5n4.dat 64 4 *Quadrature for polarised radiation, 48 | Polarised case for L=5 and n=4 49 | iql7n7.dat 64 7 *Quadrature for polarised radiation, 50 | Polarised case for L=7 and n=7 51 | iql9n8.dat 64 8 *Quadrature for polarised radiation, 52 | Polarised case for L=9 and n=8 53 | iql11n11.dat 64 11 *Quadrature for polarised radiation, 54 | Polarised case for L=11 and n=11 55 | iql13n14.dat 64 14 *Quadrature for polarised radiation, 56 | Polarised case for L=13 and n=13 57 | iql15n18.dat 64 18 *Quadrature for polarised radiation, 58 | Polarised case for L=15 and n=18 59 | -------------------------------------------------------------------------------- 60 | Note on *.dat: 61 | LX: quadrature for the order L=X, nY: quadrature with n=Y rays per octant. 62 | -------------------------------------------------------------------------------- 63 | 64 | Byte-by-byte Description of file: *.dat 65 | -------------------------------------------------------------------------------- 66 | Bytes Format Units Label Explanations 67 | -------------------------------------------------------------------------------- 68 | 1- 20 F20.18 --- weight Normalized weights over the whole sphere 69 | 22- 42 F21.18 deg incl Inclination measured from the positive Z-axis 70 | 44- 64 F21.18 deg azim Azimuth measured from the positive X-axis 71 | -------------------------------------------------------------------------------- 72 | 73 | History: 74 | From electronic version of the journal 75 | 76 | ================================================================================ 77 | (End) Patricia Vannier [CDS] 12-Mar-2020 78 | -------------------------------------------------------------------------------- /examples/plot_SimpleLineTest.py: -------------------------------------------------------------------------------- 1 | """ 2 | =============================================================== 3 | Computing a simple NLTE 8542 line profile in a FAL C atmosphere 4 | =============================================================== 5 | """ 6 | #%% 7 | # First, we import everything we need. Lightweaver is typically imported as 8 | # `lw`, but things like the library of model atoms and Fal atmospheres need to 9 | # be imported separately. 10 | from lightweaver.fal import Falc82 11 | from lightweaver.rh_atoms import H_6_atom, C_atom, O_atom, Si_atom, Al_atom, \ 12 | CaII_atom, Fe_atom, He_9_atom, MgII_atom, N_atom, Na_atom, S_atom 13 | import lightweaver as lw 14 | import matplotlib.pyplot as plt 15 | import time 16 | import numpy as np 17 | 18 | 19 | #%% 20 | # Now, we define the functions that will be used in our spectral synthesise. 21 | # First `synth_8542` which synthesises and returns the line given by an 22 | # atmosphere. 23 | def synth_8542(atmos, conserve, useNe, wave): 24 | ''' 25 | Synthesise a spectral line for given atmosphere with different 26 | conditions. 27 | 28 | Parameters 29 | ---------- 30 | atmos : lw.Atmosphere 31 | The atmospheric model in which to synthesise the line. 32 | conserve : bool 33 | Whether to start from LTE electron density and conserve charge, or 34 | simply use from the electron density present in the atomic model. 35 | useNe : bool 36 | Whether to use the electron density present in the model as the 37 | starting solution, or compute the LTE electron density. 38 | wave : np.ndarray 39 | Array of wavelengths over which to resynthesise the final line 40 | profile for muz=1. 41 | 42 | Returns 43 | ------- 44 | ctx : lw.Context 45 | The Context object that was used to compute the equilibrium 46 | populations. 47 | Iwave : np.ndarray 48 | The intensity at muz=1 for each wavelength in `wave`. 49 | ''' 50 | # Configure the atmospheric angular quadrature 51 | atmos.quadrature(5) 52 | # Configure the set of atomic models to use. 53 | aSet = lw.RadiativeSet([H_6_atom(), C_atom(), O_atom(), Si_atom(), 54 | Al_atom(), CaII_atom(), Fe_atom(), He_9_atom(), 55 | MgII_atom(), N_atom(), Na_atom(), S_atom() 56 | ]) 57 | # Set H and Ca to "active" i.e. NLTE, everything else participates as an 58 | # LTE background. 59 | aSet.set_active('H', 'Ca') 60 | # Compute the necessary wavelength dependent information (SpectrumConfiguration). 61 | spect = aSet.compute_wavelength_grid() 62 | 63 | # Either compute the equilibrium populations at the fixed electron density 64 | # provided in the model, or iterate an LTE electron density and compute the 65 | # corresponding equilibrium populations (SpeciesStateTable). 66 | if useNe: 67 | eqPops = aSet.compute_eq_pops(atmos) 68 | else: 69 | eqPops = aSet.iterate_lte_ne_eq_pops(atmos) 70 | 71 | # Configure the Context which holds the state of the simulation for the 72 | # backend, and provides the python interface to the backend. 73 | # Feel free to increase Nthreads to increase the number of threads the 74 | # program will use. 75 | ctx = lw.Context(atmos, spect, eqPops, conserveCharge=conserve, Nthreads=1) 76 | # Iterate the Context to convergence (using the iteration function now 77 | # provided by Lightweaver) 78 | lw.iterate_ctx_se(ctx) 79 | # Update the background populations based on the converged solution and 80 | # compute the final intensity for mu=1 on the provided wavelength grid. 81 | eqPops.update_lte_atoms_Hmin_pops(atmos) 82 | Iwave = ctx.compute_rays(wave, [atmos.muz[-1]], stokes=False) 83 | return ctx, Iwave 84 | 85 | 86 | #%% 87 | # The wavelength grid to output the final synthesised line on. 88 | wave = np.linspace(853.9444, 854.9444, 1001) 89 | 90 | #%% 91 | # Load an lw.Atmosphere object containing the FAL C atmosphere with 82 points 92 | # in depth, before synthesising the Ca II 8542 \AA line profile using: 93 | # 94 | # - The given electron density. 95 | # - The electron density charge conserved from a starting LTE solution. 96 | # - The LTE electron density. 97 | # 98 | # These results are then plotted. 99 | 100 | atmosRef = Falc82() 101 | ctxRef, IwaveRef = synth_8542(atmosRef, conserve=False, useNe=True, wave=wave) 102 | atmosCons = Falc82() 103 | ctxCons, IwaveCons = synth_8542(atmosCons, conserve=True, useNe=False, wave=wave) 104 | atmosLte = Falc82() 105 | ctx, IwaveLte = synth_8542(atmosLte, conserve=False, useNe=False, wave=wave) 106 | 107 | plt.plot(wave, IwaveRef, label='Reference FAL') 108 | plt.plot(wave, IwaveCons, label='Reference Cons') 109 | plt.plot(wave, IwaveLte, label='Reference LTE n_e') 110 | plt.legend() 111 | plt.show() 112 | -------------------------------------------------------------------------------- /lightweaver/nr_update.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from .atomic_set import lte_pops 4 | from .atomic_table import PeriodicTable 5 | 6 | 7 | def nr_post_update(self, fdCollisionRates=True, hOnly=False, 8 | timeDependentData=None, chunkSize=5, 9 | ngUpdate=None, printUpdate=None, extraParams=None): 10 | ''' 11 | Compute the Newton-Raphson terms for updating the electron density 12 | through charge conservation. Is attached to the Context object. 13 | Parameters 14 | ---------- 15 | fdCollisionRates : bool, optional 16 | Whether to use a finite difference approximation to the collisional 17 | rates to find the population gradient WRT ne (default: True i.e. use 18 | finite-difference, if False, collisional rates are ignored for this 19 | process.) 20 | hOnly : bool, optional 21 | Ignore atoms other than Hydrogen (the primary electron contributor) 22 | (default: False) 23 | timeDependentData : dict, optional 24 | The presence of this argument indicates that the time-dependent 25 | formalism should be used. Should contain the keys 'dt' with a 26 | floating point timestep, and 'nPrev' with a list of population 27 | vectors from the start of the time integration step, in the order of 28 | the active atoms. This latter term can be obtained from the previous 29 | state provied by `Context.time_dep_update`. 30 | chunkSize : int, optional 31 | Not currently used. 32 | ngUpdate : bool, optional 33 | Whether to apply Ng Acceleration (default: None, to apply automatic 34 | behaviour), will only accelerate if the counter on the Ng accelerator 35 | has seen enough steps since the previous acceleration (set in Context 36 | initialisation). 37 | printUpdate : bool, optional 38 | Whether to print information on the size of the update (default: 39 | None, to apply automatic behaviour). 40 | extraParams : dict, optional 41 | Dict of extra parameters to be converted through the 42 | `dict2ExtraParams` function and passed onto the C++ core. 43 | Returns 44 | ------- 45 | dPops : float 46 | The maximum relative change of any of the NLTE populations in the 47 | atmosphere. 48 | ''' 49 | if self.activeAtoms[0].element != PeriodicTable[1]: 50 | raise ValueError('Calling nr_post_update without Hydrogen active.') 51 | 52 | if ngUpdate is None: 53 | ngUpdate = self.conserveCharge 54 | 55 | if printUpdate is None: 56 | printUpdate = ngUpdate 57 | 58 | atoms = self.activeAtoms[:1] if hOnly else self.activeAtoms 59 | crswVal = self.crswCallback.val 60 | 61 | if hOnly: 62 | backgroundAtoms = [model for model in self.kwargs['spect'].radSet if model.element != PeriodicTable[1]] 63 | else: 64 | backgroundAtoms = self.kwargs['spect'].radSet.detailedAtoms + self.kwargs['spect'].radSet.passiveAtoms 65 | 66 | backgroundNe = np.zeros_like(self.atmos.ne) 67 | for atomModel in backgroundAtoms: 68 | lteStages = np.array([l.stage for l in atomModel.levels]) 69 | atom = self.kwargs['eqPops'].atomicPops[atomModel.element] 70 | backgroundNe += (lteStages[:, None] * atom.n[:, :]).sum(axis=0) 71 | 72 | neStart = np.copy(self.atmos.ne) 73 | 74 | dC = [] 75 | if fdCollisionRates: 76 | for atom in atoms: 77 | atom.compute_collisions(fillDiagonal=True) 78 | Cprev = np.copy(atom.C) 79 | pertSize = 1e-4 80 | pert = neStart * pertSize 81 | self.atmos.ne[:] += pert 82 | nStarPrev = np.copy(atom.nStar) 83 | atom.nStar[:] = lte_pops(atom.atomicModel, self.atmos.temperature, 84 | self.atmos.ne, atom.nTotal) 85 | atom.compute_collisions(fillDiagonal=True) 86 | self.atmos.ne[:] = neStart 87 | atom.nStar[:] = nStarPrev 88 | dC.append(crswVal * (atom.C - Cprev) / pert) 89 | atom.C[:] = Cprev 90 | 91 | self._nr_post_update_impl(atoms, dC, backgroundNe, 92 | timeDependentData=timeDependentData, chunkSize=chunkSize, extraParams=extraParams) 93 | self.eqPops.update_lte_atoms_Hmin_pops(self.atmos.pyAtmos, conserveCharge=False, quiet=True) 94 | 95 | if ngUpdate: 96 | update = self.rel_diff_ng_accelerate(printUpdate=printUpdate) 97 | else: 98 | update = self.rel_diff_pops(printUpdate=printUpdate) 99 | neDiff = ((np.asarray(self.atmos.ne) - neStart) 100 | / np.asarray(self.atmos.ne)) 101 | neDiffMaxIdx = neDiff.argmax() 102 | neDiffMax = neDiff[neDiffMaxIdx] 103 | update.updatedNe = True 104 | update.dNeMax = neDiffMax 105 | update.dNeMaxIdx = neDiffMaxIdx 106 | return update 107 | -------------------------------------------------------------------------------- /Utils/GenerateDispatcher.py: -------------------------------------------------------------------------------- 1 | boilerPlateNonTemplate = '''\ 2 | #include 3 | // NOTE(cmo): Machine-Generated! 4 | template 5 | inline auto dispatch_{FnName}_({ArgList}, Args&& ...args) 6 | -> {ReturnType} 7 | {{ 8 | {SwitchGenBlock} 9 | 10 | switch (dispatcher__) 11 | {{ 12 | {Cases} 13 | default: 14 | {{ 15 | assert(false); 16 | }} break; 17 | }} 18 | }} 19 | ''' 20 | 21 | boilerPlateSimdTemplate = '''\ 22 | #include 23 | // NOTE(cmo): Machine-Generated! 24 | template 25 | inline auto dispatch_{FnName}_({ArgList}, Args&& ...args) 26 | -> {ReturnType} 27 | {{ 28 | {SwitchGenBlock} 29 | 30 | switch (dispatcher__) 31 | {{ 32 | {Cases} 33 | default: 34 | {{ 35 | assert(false); 36 | }} break; 37 | }} 38 | }} 39 | ''' 40 | 41 | argName = ['first', 'second', 'third', 'fourth', 42 | 'fifth', 'sixth', 'seventh', 'eighth', 43 | 'ninth', 'tenth', 'eleventh', 'twelfth', 44 | 'thirteenth', 'fourteenth', 'fifteenth', 'sixteenth'] 45 | 46 | def bool_dispatch_template_args(numSpecs, case): 47 | args = [] 48 | for i in range(numSpecs): 49 | if case & (1 << i): 50 | args.append('true') 51 | else: 52 | args.append('false') 53 | args.append(', ') 54 | return ''.join(args[:-1]) 55 | 56 | 57 | def create_textual_specialisation_switch_bools(numSpec : int, fnName : str): 58 | if numSpec < 2: 59 | raise ValueError('Dispatcher not necessary for fewer than two specialisations.') 60 | 61 | if numSpec > 16: 62 | raise ValueError('This many specialisations seems silly.') 63 | 64 | if fnName == 'dispatcher__': 65 | raise ValueError('The name dispatcher__ is used internally to this function') 66 | 67 | formatArgs = {} 68 | formatArgs['FnName'] = fnName 69 | formatArgs['ArgList'] = ''.join([x for i in range(numSpec) for x in ('bool ', argName[i], ', ')][:-1]) 70 | formatArgs['ReturnType'] = f"decltype({fnName}<{(' '.join(['false,' for _ in range(numSpec)]))[:-1]}>(std::forward(args)...))" 71 | 72 | switchGenBuilder = [f'u32 dispatcher__ = {argName[0]};\n'] 73 | for i in range(1, numSpec): 74 | switchGenBuilder.append(f'dispatcher__ += {argName[i]} << {i};\n') 75 | switchGenBlock = ''.join(switchGenBuilder) 76 | switchGenBlock = ' ' + switchGenBlock.replace('\n', '\n ') 77 | formatArgs['SwitchGenBlock'] = switchGenBlock 78 | 79 | casesBuilder = [] 80 | for case in range(1 << numSpec): 81 | casesBuilder.append(f'case {case}:\n') 82 | casesBuilder.append('{\n') 83 | casesBuilder.append(f' return {fnName}<{bool_dispatch_template_args(numSpec, case)}>(std::forward(args)...);\n') 84 | casesBuilder.append('} break;\n') 85 | cases = ''.join(casesBuilder) 86 | cases = ' ' + cases.replace('\n', '\n ') 87 | formatArgs['Cases'] = cases 88 | 89 | return boilerPlateNonTemplate.format(**formatArgs) 90 | 91 | def create_textual_specialisation_switch_bools_simd(numSpec : int, fnName : str): 92 | if numSpec < 2: 93 | raise ValueError('Dispatcher not necessary for fewer than two specialisations.') 94 | 95 | if numSpec > 16: 96 | raise ValueError('This many specialisations seems silly.') 97 | 98 | if fnName == 'dispatcher__': 99 | raise ValueError('The name dispatcher__ is used internally to this function') 100 | 101 | formatArgs = {} 102 | formatArgs['FnName'] = fnName 103 | formatArgs['ArgList'] = ''.join([x for i in range(numSpec) for x in ('bool ', argName[i], ', ')][:-1]) 104 | formatArgs['ReturnType'] = f"decltype({fnName}(std::forward(args)...))" 105 | 106 | switchGenBuilder = [f'u32 dispatcher__ = {argName[0]};\n'] 107 | for i in range(1, numSpec): 108 | switchGenBuilder.append(f'dispatcher__ += {argName[i]} << {i};\n') 109 | switchGenBlock = ''.join(switchGenBuilder) 110 | switchGenBlock = ' ' + switchGenBlock.replace('\n', '\n ') 111 | formatArgs['SwitchGenBlock'] = switchGenBlock 112 | 113 | casesBuilder = [] 114 | for case in range(1 << numSpec): 115 | casesBuilder.append(f'case {case}:\n') 116 | casesBuilder.append('{\n') 117 | casesBuilder.append(f' return {fnName}(std::forward(args)...);\n') 118 | casesBuilder.append('} break;\n') 119 | cases = ''.join(casesBuilder) 120 | cases = ' ' + cases.replace('\n', '\n ') 121 | formatArgs['Cases'] = cases 122 | 123 | return boilerPlateSimdTemplate.format(**formatArgs) 124 | 125 | 126 | if __name__ == '__main__': 127 | functions = [('chi_eta_aux_accum', 4), ('intensity_core_opt', 4), 128 | ('compute_full_operator_rates', 2)] 129 | 130 | for fnName, nSpec in functions: 131 | with open(f'Dispatch_{fnName}.ipp', 'w') as f: 132 | f.write(create_textual_specialisation_switch_bools_simd(nSpec, fnName)) -------------------------------------------------------------------------------- /Source/LwFormalInterface.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LW_FORMAL_INTERFACE_HPP 2 | #define CMO_LW_FORMAL_INTERFACE_HPP 3 | 4 | #include "Constants.hpp" 5 | #include "LwAtmosphere.hpp" 6 | #include "LwIterationResult.hpp" 7 | #include "LwExtraParams.hpp" 8 | 9 | struct Context; 10 | struct Atom; 11 | struct Transition; 12 | struct PlatformSharedLibrary; 13 | namespace LwInternal 14 | { 15 | bool load_library(PlatformSharedLibrary* lib, const char* path); 16 | template F load_function(PlatformSharedLibrary lib, const char* name); 17 | void close_library(PlatformSharedLibrary lib); 18 | } 19 | 20 | #ifdef _WIN32 21 | #include "LwFormalInterfaceWin.hpp" 22 | #else 23 | #include "LwFormalInterfacePosix.hpp" 24 | #endif 25 | 26 | #include 27 | 28 | namespace LwInternal 29 | { 30 | struct FormalData; 31 | } 32 | struct Context; 33 | struct NrTimeDependentData; 34 | 35 | typedef void(*LwFsFn)(LwInternal::FormalData* fd, int la, int mu, 36 | bool toObs, const F64View1D& wav); 37 | struct FormalSolver 38 | { 39 | LwFsFn solver; 40 | int Ndim; 41 | int width; // NOTE(cmo): For SIMD later on. 42 | const char* name; 43 | }; 44 | 45 | typedef FormalSolver(*FsProvider)(); 46 | 47 | struct FormalSolverManager 48 | { 49 | std::vector formalSolvers; 50 | std::vector libs; 51 | 52 | FormalSolverManager(); 53 | bool load_fs_from_path(const char* path); 54 | }; 55 | 56 | typedef f64(*Interp2d)(const IntersectionData& grid, const IntersectionResult& loc, 57 | const F64View2D& param); 58 | 59 | struct InterpFn 60 | { 61 | Interp2d interp_2d; 62 | int Ndim; 63 | const char* name; 64 | InterpFn() : interp_2d(nullptr), 65 | Ndim(), 66 | name("") 67 | {} 68 | InterpFn(int _Ndim, const char* _name, Interp2d interp) : interp_2d(interp), 69 | Ndim(_Ndim), 70 | name(_name) 71 | {} 72 | }; 73 | 74 | typedef InterpFn(*InterpProvider)(); 75 | 76 | struct InterpFnManager 77 | { 78 | std::vector fns; 79 | std::vector libs; 80 | 81 | InterpFnManager(); 82 | bool load_fn_from_path(const char* path); 83 | }; 84 | 85 | struct PrdIterData; 86 | typedef IterationResult(*FormalSolIterFn)(Context& ctx, bool lambdaIterate, ExtraParams params); 87 | typedef IterationResult(*SimpleFormalSol)(Context& ctx, bool upOnly, ExtraParams params); 88 | typedef IterationResult(*FullStokesFormalSol)(Context& ctx, bool updateJ, bool upOnly, 89 | ExtraParams params); 90 | typedef IterationResult(*RedistPrdLinesFn)(Context& ctx, int maxIter, f64 tol, ExtraParams params); 91 | typedef void(*StatEqFn)(Atom* atom, ExtraParams params, int spaceStart, int spaceEnd); 92 | typedef void(*TimeDepUpdateFn)(Atom* atomIn, F64View2D nOld, f64 dt, ExtraParams params, 93 | int spaceStart, int spaceEnd); 94 | typedef void(*NrPostUpdateFn)(Context& ctx, std::vector* atoms, 95 | const std::vector& dC, 96 | F64View backgroundNe, 97 | const NrTimeDependentData& timeDepData, 98 | f64 crswVal, 99 | ExtraParams params, 100 | int spaceStart, int spaceEnd); 101 | 102 | typedef void(*AllocPerAtomScratch)(Atom* atom, bool detailedStatic); 103 | typedef void(*FreePerAtomScratch)(Atom* atom); 104 | typedef void(*AllocPerTransScratch)(Transition* trans); 105 | typedef void(*FreePerTransScratch)(Transition* trans); 106 | typedef void(*AllocGlobalScratch)(Context* ctx); 107 | typedef void(*FreeGlobalScratch)(Context* ctx); 108 | typedef void(*AccumulateOverThreads)(Context* ctx); 109 | 110 | struct FsIterationFns 111 | { 112 | int Ndim; 113 | bool dimensionSpecific; 114 | bool respectsFormalSolver; 115 | bool defaultPerAtomStorage; 116 | bool defaultWlaGijStorage; 117 | const char* name; 118 | 119 | FormalSolIterFn fs_iter; 120 | SimpleFormalSol simple_fs; 121 | FullStokesFormalSol full_stokes_fs; 122 | RedistPrdLinesFn redistribute_prd; 123 | StatEqFn stat_eq; 124 | TimeDepUpdateFn time_dep_update; 125 | NrPostUpdateFn nr_post_update; 126 | 127 | AllocPerAtomScratch alloc_per_atom; 128 | FreePerAtomScratch free_per_atom; 129 | AllocPerTransScratch alloc_per_trans; 130 | FreePerTransScratch free_per_trans; 131 | AllocGlobalScratch alloc_global_scratch; 132 | FreeGlobalScratch free_global_scratch; 133 | AccumulateOverThreads accumulate_over_threads; 134 | }; 135 | 136 | typedef FsIterationFns(*FsIterationFnsProvider)(); 137 | 138 | struct FsIterationFnsManager 139 | { 140 | std::vector fns; 141 | std::vector libs; 142 | 143 | FsIterationFnsManager(); 144 | bool load_fns_from_path(const char* path); 145 | }; 146 | 147 | #else 148 | #endif -------------------------------------------------------------------------------- /lightweaver/iteration_update.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import TYPE_CHECKING, List 3 | 4 | if TYPE_CHECKING: 5 | from . import Context 6 | 7 | @dataclass 8 | class IterationUpdate: 9 | ''' 10 | Stores the results of an iteration of one of the backend functions, and 11 | determines how to format this for printing. All changes refer to relative 12 | change. 13 | 14 | Attributes 15 | ---------- 16 | ctx : Context 17 | The context with which this update is associated. 18 | crsw : float 19 | The current value of the collisional radiative switching parameter. 20 | updatedJ : bool 21 | Whether the iteration affected the global J grid. 22 | dJMax : float 23 | The maximum change in J. 24 | dJMaxIdx : int 25 | The index of the maximum change of J in a flattened array of J. 26 | updatedPops : bool 27 | Whether the active atomic populations were modified by the iteration. 28 | dPops : List[float] 29 | The maximum change in each active population. 30 | dPopsMaxIdx : List[int] 31 | The location of the maximum change in each population in the flattened 32 | population array. 33 | ngAccelerated : List[bool] 34 | Whether the atomic populations were modified by Ng Acceleration (per species, due to thresholding). 35 | updatedNe : bool 36 | Whether the electron density in the atmosphere was affected by the iteration. 37 | dNeMax : float 38 | The maximum change in the electron density. 39 | dNeMaxIdx : int 40 | The location of the maximum change in the electron density array. 41 | updatedRho : bool 42 | Whether the iteration affected the value of rhoPrd on PRD lines. 43 | NprdSubIter : int 44 | The number of PRD sub-iterations taken (if multiple), 45 | dRho : List[float] 46 | The maximum change in rho for each spectral line treated with PRD, in 47 | the order of the lines on each activeAtom. These values are repeated for 48 | each sub-iteration < NprdSubIter. 49 | dRhoMaxIdx : List[int] 50 | The location of the maximum change in rho for each PRD line. 51 | updatedJPrd : bool 52 | Whether the PRD iteration affected J. 53 | dJPrdMax : float 54 | The maximum change in J during each PRD sub-iteration. 55 | dJPrdMaxIdx : int 56 | The location of the maximum change in J for each PRD sub-iteration. 57 | dPopsMax : float 58 | The maximum population change (including ne) over the iteration 59 | (read-only property). 60 | dRhoMax : float 61 | The maximum change in the PRD rho value for any line in the final 62 | subiteration (read-only property). 63 | ''' 64 | ctx: 'Context' 65 | crsw: float = 1.0 66 | updatedJ: bool = False 67 | dJMax: float = 0.0 68 | dJMaxIdx: int = 0 69 | 70 | updatedPops: bool = False 71 | dPops: List[float] = field(default_factory=list) 72 | dPopsMaxIdx: List[int] = field(default_factory=list) 73 | ngAccelerated: List[bool] = field(default_factory=list) 74 | 75 | updatedNe: bool = False 76 | dNeMax: float = 0.0 77 | dNeMaxIdx: int = 0 78 | 79 | updatedRho: bool = False 80 | NprdSubIter: int = 0 81 | dRho: List[float] = field(default_factory=list) 82 | dRhoMaxIdx: List[int] = field(default_factory=list) 83 | updatedJPrd: bool = False 84 | dJPrdMax: List[float] = field(default_factory=list) 85 | dJPrdMaxIdx: List[int] = field(default_factory=list) 86 | 87 | @property 88 | def dPopsMax(self) -> float: 89 | if len(self.dPops) == 0: 90 | if self.updatedNe: 91 | return self.dNeMax 92 | else: 93 | return 0.0 94 | 95 | result = max(self.dPops) 96 | if self.updatedNe: 97 | result = max(result, self.dNeMax) 98 | return result 99 | 100 | @property 101 | def dRhoMax(self) -> float: 102 | if self.NprdSubIter == 0: 103 | return 0.0 104 | finalSubIterStart = (self.NprdSubIter - 1) * self.ctx.kwargs['spect'].NprdTrans 105 | return max(self.dRho[finalSubIterStart:]) 106 | 107 | def compact_representation(self): 108 | ''' 109 | Produce a compact string representation of the object (similar to 110 | Lightweaver < v0.8). 111 | ''' 112 | chunks = [] 113 | if self.crsw != 1.0: 114 | chunks.append(f'CRSW: {self.crsw:.2e}') 115 | 116 | if self.updatedJ: 117 | chunks.append(f'dJ = {self.dJMax:.2e}') 118 | 119 | if self.updatedPops: 120 | for idx, delta in enumerate(self.dPops): 121 | atomName = self.ctx.activeAtoms[idx].atomicModel.element.name 122 | accel = ' (accelerated)' if self.ngAccelerated[idx] else '' 123 | chunks.append(f' {atomName} delta = {delta:6.4e}{accel}') 124 | 125 | if self.updatedNe: 126 | delta = self.dNeMax 127 | chunks.append(f' ne delta = {delta:6.4e}') 128 | 129 | if self.updatedRho: 130 | iterCount = self.NprdSubIter 131 | dRhoMax = self.dRhoMax 132 | chunks.append(f' PRD dRho = {dRhoMax:.2e}, (sub-iterations: {iterCount})') 133 | 134 | return '\n'.join(chunks) 135 | -------------------------------------------------------------------------------- /Source/LwTransition.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LW_TRANSITION_HPP 2 | #define CMO_LW_TRANSITION_HPP 3 | 4 | #include "CmoArray.hpp" 5 | #include "LwAtmosphere.hpp" 6 | #include "LwMisc.hpp" 7 | 8 | #include 9 | #include 10 | 11 | enum TransitionType 12 | { 13 | LINE, 14 | CONTINUUM 15 | }; 16 | 17 | namespace LwInternal 18 | { 19 | struct ThreadData; 20 | } 21 | 22 | struct Transition 23 | { 24 | // NOTE(cmo): Transitions are often queried only for whether they are active, so 25 | // bring Nblue and Nred in on the first cache line 26 | int Nblue; 27 | int Nred; 28 | 29 | enum TransitionType type; 30 | int i; 31 | int j; 32 | 33 | f64 Aji; 34 | f64 Bji; 35 | f64 Bij; 36 | f64 lambda0; 37 | f64 dopplerWidth; 38 | 39 | F64View wavelength; 40 | F64View gij; 41 | F64View alpha; 42 | F64View4D phi; 43 | F64View wphi; 44 | bool polarised; 45 | F64View4D phiQ; 46 | F64View4D phiU; 47 | F64View4D phiV; 48 | F64View4D psiQ; 49 | F64View4D psiU; 50 | F64View4D psiV; 51 | F64View Qelast; 52 | F64View aDamp; 53 | // NOTE(cmo): This was probably a bad idea for cache locality, and I can't 54 | // remember why I did it. 55 | BoolView active; 56 | 57 | F64View Rij; 58 | F64View Rji; 59 | F64View2D rhoPrd; 60 | 61 | void* methodScratch; 62 | 63 | Prd::PrdStorage* prdData; 64 | 65 | Prd::RhoCoeffView hPrdCoeffs; 66 | Prd::PrdStorage prdStorage; 67 | 68 | Transition() : prdData(&prdStorage) 69 | {} 70 | 71 | inline f64 wlambda(int la) const 72 | { 73 | if (la == 0) 74 | return 0.5 * (wavelength(1) - wavelength(0)) * dopplerWidth; 75 | 76 | int len = wavelength.shape(0); 77 | if (la == len-1) 78 | return 0.5 * (wavelength(len-1) - wavelength(len-2)) * dopplerWidth; 79 | 80 | return 0.5 * (wavelength(la+1) - wavelength(la-1)) * dopplerWidth; 81 | } 82 | 83 | inline int lt_idx(int la) const 84 | { 85 | return la - Nblue; 86 | } 87 | 88 | inline bool is_active(int la) const 89 | { 90 | return (la >= Nblue) && (la < Nred); 91 | } 92 | 93 | inline void uv(int la, int mu, bool toObs, F64View Uji, F64View Vij, F64View Vji) const 94 | { 95 | namespace C = Constants; 96 | int lt = lt_idx(la); 97 | 98 | if (type == TransitionType::LINE) 99 | { 100 | constexpr f64 hc_4pi = 0.25 * C::HC / C::Pi; 101 | // NOTE(cmo): This term (hnu_4pi) added to improve treatment of emissivities 102 | // opacities if they are calculated across very wide wavelength 103 | // ranges, rather than just the standard range used to integrate the 104 | // line rates. This happens, for example in ctx.compute_rays. 105 | // The result of this is the complete hnu/4pi * phi expression is 106 | // used, rather than the hc/4pi*phi_num expression from the paper. 107 | const f64 hnu_4pi = hc_4pi * (lambda0 / wavelength(lt)); 108 | auto p = phi(lt, mu, (int)toObs); 109 | for (int k = 0; k < Vij.shape(0); ++k) 110 | { 111 | // Vij(k) = hc_4pi * Bij * p(k); 112 | // NOTE(cmo): Updated term 113 | Vij(k) = hnu_4pi * Bij * p(k); 114 | Vji(k) = gij(k) * Vij(k); 115 | } 116 | // NOTE(cmo): Do the HPRD linear interpolation on rho here 117 | // As we make Uji, Vij, and Vji explicit, there shouldn't be any need for direct access to gij 118 | if (hPrdCoeffs) 119 | { 120 | for (int k = 0; k < Vij.shape(0); ++k) 121 | { 122 | const auto& coeffs = hPrdCoeffs(lt, mu, toObs, k); 123 | f64 rho = (1.0 - coeffs.frac) * rhoPrd(coeffs.i0, k) + coeffs.frac * rhoPrd(coeffs.i1, k); 124 | Vji(k) *= rho; 125 | } 126 | } 127 | for (int k = 0; k < Vij.shape(0); ++k) 128 | { 129 | Uji(k) = Aji / Bji * Vji(k); 130 | } 131 | } 132 | else 133 | { 134 | constexpr f64 twoHc = 2.0 * C::HC / cube(C::NM_TO_M); 135 | const f64 hcl = twoHc / cube(wavelength(lt)); 136 | const f64 a = alpha(lt); 137 | for (int k = 0; k < Vij.shape(0); ++k) 138 | { 139 | Vij(k) = a; 140 | Vji(k) = gij(k) * Vij(k); 141 | Uji(k) = hcl * Vji(k); 142 | } 143 | } 144 | } 145 | 146 | inline void zero_rates() 147 | { 148 | Rij.fill(0.0); 149 | Rji.fill(0.0); 150 | } 151 | 152 | inline void recompute_gII() 153 | { 154 | // NOTE(cmo): This is just a flag to recompute it in a deferred manner 155 | // next time the prd_scatter function is run. 156 | prdStorage.upToDate = false; 157 | } 158 | 159 | void compute_phi(const Atmosphere& atmos, F64View aDamp, F64View vBroad); 160 | void compute_phi_la(const Atmosphere& atmos, const F64View& aDamp, const F64View& vBroad, int lt); 161 | void compute_phi_parallel(LwInternal::ThreadData* threading, const Atmosphere& atmos, F64View aDamp, F64View vBroad); 162 | std::function bound_parallel_compute_phi; 163 | void compute_wphi(const Atmosphere& atmos); 164 | void compute_polarised_profiles(const Atmosphere& atmos, F64View aDamp, F64View vBroad, const ZeemanComponents& z); 165 | }; 166 | 167 | #else 168 | #endif -------------------------------------------------------------------------------- /Source/LwAtmosphere.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_LW_ATMOSPHERE_HPP 2 | #define CMO_LW_ATMOSPHERE_HPP 3 | 4 | #include "CmoArray.hpp" 5 | 6 | enum RadiationBc 7 | { 8 | UNINITIALISED, 9 | ZERO, 10 | THERMALISED, 11 | PERIODIC, 12 | CALLABLE 13 | }; 14 | 15 | typedef Jasnah::Array2NonOwn BcIdxs; 16 | 17 | struct AtmosphericBoundaryCondition 18 | { 19 | RadiationBc type; 20 | F64Arr3D bcData; 21 | BcIdxs idxs; 22 | 23 | AtmosphericBoundaryCondition() : type(RadiationBc::ZERO), bcData() 24 | {} 25 | 26 | AtmosphericBoundaryCondition(RadiationBc typ, int Nwave, int Nmu, 27 | int Nspace, BcIdxs indexVector) 28 | : type(typ), 29 | bcData(), 30 | idxs(indexVector) 31 | { 32 | if (type == RadiationBc::CALLABLE) 33 | bcData = F64Arr3D(Nwave, Nmu, Nspace); 34 | } 35 | 36 | void set_bc_data(F64View3D data) 37 | { 38 | for (int la = 0; la < bcData.shape(0); ++la) 39 | for (int mu = 0; mu < bcData.shape(1); ++mu) 40 | for (int k = 0; k < bcData.shape(2); ++k) 41 | bcData(la, mu, k) = data(la, mu, k); 42 | } 43 | }; 44 | 45 | enum class InterpolationAxis 46 | { 47 | None, 48 | X, 49 | Z 50 | }; 51 | 52 | template 53 | bool approx_equal(T a, T b, T eps) 54 | { 55 | // https://floating-point-gui.de/errors/comparison/ 56 | 57 | const T absA = std::abs(a); 58 | const T absB = std::abs(b); 59 | const T diff = std::abs(a - b); 60 | 61 | if (a == b) 62 | { 63 | return true; 64 | } 65 | else if (a == 0.0 || b == 0.0 || (absA + absB < std::numeric_limits::min())) 66 | { 67 | // NOTE(cmo): min in std::numeric_limits is the minimum normalised value. 68 | return diff < (eps * std::numeric_limits::min()); 69 | } 70 | else 71 | { 72 | return diff / min(absA + absB, std::numeric_limits::max()) < eps; 73 | } 74 | } 75 | 76 | struct IntersectionData 77 | { 78 | F64View x; 79 | F64View z; 80 | f64 mux; 81 | f64 muz; 82 | f64 xWrapVal; 83 | bool toObs; 84 | int xStep; 85 | int xStart; 86 | int xEnd; 87 | int zStep; 88 | int zStart; 89 | int zEnd; 90 | bool periodic; 91 | }; 92 | 93 | 94 | struct IntersectionResult 95 | { 96 | InterpolationAxis axis; 97 | f64 fractionalZ; 98 | f64 fractionalX; 99 | f64 distance; 100 | 101 | static constexpr f64 PlaneTol = 1e-6; 102 | 103 | IntersectionResult() : axis(InterpolationAxis::None), 104 | fractionalZ(0.0), 105 | fractionalX(0.0), 106 | distance(0.0) 107 | {} 108 | 109 | IntersectionResult(InterpolationAxis a, f64 fracZ, f64 fracX, f64 dist) 110 | : axis(a), 111 | fractionalZ(fracZ), 112 | fractionalX(fracX), 113 | distance(dist) 114 | { 115 | switch (axis) 116 | { 117 | case InterpolationAxis::X: 118 | { 119 | f64 x = std::round(fractionalX); 120 | if (approx_equal(x, fractionalX, PlaneTol)) 121 | { 122 | axis = InterpolationAxis::None; 123 | fractionalX = x; 124 | } 125 | } break; 126 | 127 | case InterpolationAxis::Z: 128 | { 129 | f64 z = std::round(fractionalZ); 130 | if (approx_equal(z, fractionalZ, PlaneTol)) 131 | { 132 | axis = InterpolationAxis::None; 133 | fractionalZ = z; 134 | } 135 | } break; 136 | 137 | default: 138 | break; 139 | } 140 | 141 | 142 | } 143 | }; 144 | 145 | struct InterpolationStencil 146 | { 147 | IntersectionResult uwIntersection; 148 | IntersectionResult dwIntersection; 149 | int longCharIdx; 150 | int substepIdx; 151 | }; 152 | 153 | struct SubstepIntersections 154 | { 155 | std::vector steps; 156 | }; 157 | 158 | struct Intersections 159 | { 160 | Jasnah::Array4Own intersections; 161 | std::vector substeps; 162 | 163 | void init(int mu, int Nx, int Nz) 164 | { 165 | intersections = Jasnah::Array4Own(mu, 2, Nx, Nz); 166 | substeps.reserve(2 * mu * Nz); 167 | } 168 | 169 | explicit operator bool() 170 | { 171 | return bool(intersections); 172 | } 173 | }; 174 | 175 | struct Atmosphere 176 | { 177 | int Nspace; 178 | int Nrays; 179 | int Ndim; 180 | int Nx; 181 | int Ny; 182 | int Nz; 183 | int Noutgoing; 184 | F64View x; 185 | F64View y; 186 | F64View z; 187 | F64View height; 188 | F64View temperature; 189 | F64View ne; 190 | F64View vx; 191 | F64View vy; 192 | F64View vz; 193 | F64View2D vlosMu; 194 | F64View B; 195 | F64View gammaB; 196 | F64View chiB; 197 | F64View2D cosGamma; 198 | F64View2D cos2chi; 199 | F64View2D sin2chi; 200 | F64View vturb; 201 | F64View nHTot; 202 | F64View muz; 203 | F64View muy; 204 | F64View mux; 205 | F64View wmu; 206 | 207 | AtmosphericBoundaryCondition xLowerBc; 208 | AtmosphericBoundaryCondition xUpperBc; 209 | AtmosphericBoundaryCondition yLowerBc; 210 | AtmosphericBoundaryCondition yUpperBc; 211 | AtmosphericBoundaryCondition zLowerBc; 212 | AtmosphericBoundaryCondition zUpperBc; 213 | 214 | Intersections intersections; 215 | 216 | void update_projections(); 217 | }; 218 | 219 | void build_intersection_list(Atmosphere* atmos); 220 | 221 | #else 222 | #endif -------------------------------------------------------------------------------- /Source/Ng.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_NG_HPP 2 | #define CMO_NG_HPP 3 | 4 | #include "Constants.hpp" 5 | #include "CmoArray.hpp" 6 | #include "LuSolve.hpp" 7 | #include 8 | #include 9 | 10 | struct NgChange 11 | { 12 | f64 dMax; 13 | i64 dMaxIdx; 14 | }; 15 | 16 | struct NgArgs { 17 | int nOrder; 18 | int nPeriod; 19 | int nDelay; 20 | f64 threshold; 21 | f64 lowerThreshold; 22 | }; 23 | 24 | struct Ng 25 | { 26 | int len; 27 | int Norder; 28 | int Nperiod; 29 | int Ndelay; 30 | f64 threshold; 31 | f64 lowerThreshold; 32 | F64Arr2D previous; 33 | F64Arr1D prevRelChange; 34 | NgChange prevNgChange; 35 | int count; 36 | bool init; 37 | 38 | Ng() : len(0), Norder(0), Nperiod(0), Ndelay(0), 39 | threshold(0.0), lowerThreshold(0.0), 40 | previous{}, prevRelChange{}, prevNgChange{}, 41 | count(0), init(false) 42 | {} 43 | 44 | Ng(const NgArgs& args, F64View sol) 45 | : len(sol.shape(0)), Norder(args.nOrder), Nperiod(args.nPeriod), 46 | Ndelay(max(args.nDelay, args.nPeriod+2)), 47 | threshold(args.threshold), lowerThreshold(args.lowerThreshold), 48 | previous(0.0, Norder+2, len), prevRelChange(10.0, Norder+2), 49 | prevNgChange{}, count(0), init(true) 50 | { 51 | auto storage = previous(count); 52 | for (int k = 0; k < len; ++k) 53 | storage(k) = sol(k); 54 | count += 1; 55 | } 56 | 57 | Ng(const Ng& other) = default; 58 | Ng(Ng&& other) = default; 59 | Ng& operator=(const Ng& other) = default; 60 | Ng& operator=(Ng&& other) = default; 61 | 62 | inline int storage_index(int cnt) 63 | { 64 | return cnt % (Norder + 2); 65 | } 66 | 67 | bool accelerate(F64View sol) 68 | { 69 | if (!init) 70 | { 71 | // NOTE(cmo): If we got to here without being initialised then we're just being used for max_change 72 | len = sol.shape(0); 73 | previous = F64Arr2D(0.0, 2, len); 74 | prevRelChange = F64Arr1D(1.0, 2); 75 | init = true; 76 | } 77 | 78 | auto idx = storage_index(count); 79 | for (int k = 0; k < len; ++k) 80 | previous(idx, k) = sol(k); 81 | count += 1; 82 | compute_max_change(); 83 | prevRelChange(idx) = prevNgChange.dMax; 84 | 85 | if (!((Norder > 0) 86 | && (count >= Ndelay) 87 | && ((count - Ndelay) % Nperiod) == 0) 88 | ) 89 | return false; 90 | 91 | if (prevNgChange.dMax < lowerThreshold) 92 | return false; 93 | 94 | for (int i = 0; i < prevRelChange.shape(0); ++i) 95 | { 96 | if (prevRelChange(i) > threshold) { 97 | return false; 98 | } 99 | } 100 | 101 | auto Delta = F64Arr2D(Norder + 1, len); 102 | auto weight = F64Arr(len); 103 | 104 | for (int i = 0; i <= Norder; ++i) 105 | { 106 | int ip = storage_index(count - i - 1); 107 | int ipp = storage_index(count - i - 2); 108 | for (int k = 0; k < len; ++k) 109 | Delta(i, k) = previous(ip, k) - previous(ipp, k); 110 | } 111 | for (int k = 0; k < len; ++k) 112 | weight(k) = 1.0 / abs(sol(k)); 113 | 114 | auto A = F64Arr2D(0.0, Norder, Norder); 115 | auto b = F64Arr1D(0.0, Norder); 116 | for (int j = 0; j < Norder; ++j) 117 | { 118 | for (int k = 0; k < len; ++k) 119 | b(j) += weight(k) 120 | * Delta(0, k) 121 | * (Delta(0, k) - Delta(j+1, k)); 122 | 123 | for (int i = 0; i < Norder; ++i) 124 | for (int k = 0; k < len; ++k) 125 | A(i,j) += weight(k) 126 | * (Delta(j+1, k) - Delta(0, k)) 127 | * (Delta(i+1, k) - Delta(0, k)); 128 | } 129 | solve_lin_eq(A, b); 130 | 131 | int i0 = storage_index(count - 1); 132 | for (int i = 0; i < Norder; ++i) 133 | { 134 | int ip = storage_index(count - i - 2); 135 | for (int k = 0; k < len; ++k) 136 | sol(k) += b(i) * (previous(ip, k) - previous(i0, k)); 137 | } 138 | for (int k = 0; k < len; ++k) 139 | previous(i0, k) = sol(k); 140 | 141 | return true; 142 | } 143 | 144 | inline NgChange relative_change_from_prev(F64View newSol) 145 | { 146 | if (!init || count < 1) 147 | return { 0.0, 0 }; 148 | 149 | auto sol = previous(storage_index(count-1)); 150 | if (newSol.shape(0) != len) 151 | return { 0.0, 0 }; 152 | 153 | f64 dMax = 0.0; 154 | int maxIdx = 0; 155 | for (int k = 0; k < len; ++k) 156 | { 157 | if (newSol(k) != 0.0) 158 | { 159 | f64 change = abs((newSol(k) - sol(k)) / newSol(k)); 160 | dMax = max_idx(dMax, change, maxIdx, k); 161 | } 162 | } 163 | return { dMax, maxIdx }; 164 | } 165 | 166 | inline NgChange compute_max_change() 167 | { 168 | if (!init || count < 2) 169 | return { 0.0, 0 }; 170 | 171 | auto old = previous(storage_index(count-2)); 172 | auto current = previous(storage_index(count-1)); 173 | f64 dMax = 0.0; 174 | int maxIdx = 0; 175 | for (int k = 0; k < len; ++k) 176 | { 177 | if (current(k) != 0.0) 178 | { 179 | f64 change = abs((current(k) - old(k)) / current(k)); 180 | dMax = max_idx(dMax, change, maxIdx, k); 181 | } 182 | } 183 | prevNgChange = { dMax, maxIdx }; 184 | return { dMax, maxIdx }; 185 | } 186 | 187 | inline NgChange max_change() 188 | { 189 | return prevNgChange; 190 | } 191 | 192 | inline void clear() 193 | { 194 | previous.fill(0); 195 | count = 0; 196 | } 197 | }; 198 | 199 | #endif -------------------------------------------------------------------------------- /lightweaver/barklem.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Sequence, Tuple 2 | 3 | import numpy as np 4 | from scipy.interpolate import RectBivariateSpline 5 | from scipy.special import gamma 6 | 7 | import lightweaver.constants as Const 8 | from .atomic_table import PeriodicTable 9 | from .utils import get_data_path 10 | 11 | if TYPE_CHECKING: 12 | from .atomic_model import AtomicLine, AtomicModel 13 | 14 | DeltaNeff = 0.1 15 | 16 | class BarklemTable: 17 | ''' 18 | Storage for each table of Barklem data for Van der Waals approximation. 19 | ''' 20 | def __init__(self, path: str, neff0: Tuple[float, float]): 21 | data = np.genfromtxt(path, comments='c') 22 | shape = data.shape 23 | self.cross = data[:shape[0]//2] 24 | self.alpha = data[shape[0]//2:] 25 | 26 | self.neff1 = neff0[0] + np.arange(shape[0]//2) * DeltaNeff 27 | self.neff2 = neff0[1] + np.arange(shape[1]) * DeltaNeff 28 | 29 | class BarklemCrossSectionError(Exception): 30 | ''' 31 | Raised if the Barklem cross-section cannot be applied to the atom in 32 | question. 33 | ''' 34 | pass 35 | 36 | class Barklem: 37 | ''' 38 | Storage for all three Barklem cross-section cases and application via the 39 | `get_active_cross_section` function. 40 | ''' 41 | barklem_sp = BarklemTable(get_data_path() + 'Barklem_spdata.dat', (1.0, 1.3)) 42 | barklem_pd = BarklemTable(get_data_path() + 'Barklem_pddata.dat', (1.3, 2.3)) 43 | barklem_df = BarklemTable(get_data_path() + 'Barklem_dfdata.dat', (2.3, 3.3)) 44 | 45 | @classmethod 46 | def get_active_cross_section(cls, atom: 'AtomicModel', 47 | line: 'AtomicLine', 48 | vals: Sequence[float]) -> Sequence[float]: 49 | ''' 50 | Returns the cross section data for use in the Van der Waals collisional 51 | broadening routines. 52 | See: 53 | 54 | - Anstee & O'Mara 1995, MNRAS 276, 859-866 55 | 56 | - Barklem & O'Mara 1998, MNRAS 300, 863-871 57 | 58 | - Unsold: 59 | 60 | - Traving 1960, "Uber die Theorie der Druckverbreiterung 61 | von Spektrallinien", p 91-97 62 | 63 | - Mihalas 1978, p. 282ff, and Table 9-1/ 64 | 65 | Returns 66 | ------- 67 | result : list of 3 float 68 | Barklem cross-section, Barklem alpha, Helium contribution 69 | following Unsold (always 1.0) 70 | ''' 71 | i = line.i 72 | j = line.j 73 | 74 | SOrbit = 0 75 | POrbit = 1 76 | DOrbit = 2 77 | FOrbit = 3 78 | 79 | result = [vals[0], vals[1], 0.0] 80 | 81 | # Follows original RH version. Interpolate tables if sigma < 20.0, otherwise 82 | # assume the provided values are the coefficients 83 | if vals[0] < 20.0: 84 | if atom.levels[i].stage > 0: 85 | raise BarklemCrossSectionError('Atom is not neutral.') 86 | 87 | # Find principal quantum numbers 88 | # try: 89 | # lowerNum = determinate(atom.levels[i]) 90 | # upperNum = determinate(atom.levels[j]) 91 | # except CompositeLevelError: 92 | # raise BarklemCrossSectionError() 93 | lowerNum = atom.levels[i].L 94 | upperNum = atom.levels[j].L 95 | if lowerNum is None or upperNum is None: 96 | raise BarklemCrossSectionError('L not provided for levels.') 97 | 98 | nums = (lowerNum, upperNum) 99 | 100 | # Check is a Barklem case applies 101 | if nums == (SOrbit, POrbit) or nums == (POrbit, SOrbit): 102 | table = cls.barklem_sp 103 | elif nums == (POrbit, DOrbit) or nums == (DOrbit, POrbit): 104 | table = cls.barklem_pd 105 | elif nums == (DOrbit, FOrbit) or nums == (FOrbit, DOrbit): 106 | table = cls.barklem_df 107 | else: 108 | raise BarklemCrossSectionError('Not a valid shell combination.') 109 | 110 | Z = atom.levels[j].stage + 1 111 | # Find index of continuum level 112 | ic = j + 1 113 | while atom.levels[ic].stage < atom.levels[j].stage + 1: 114 | ic += 1 115 | 116 | deltaEi = (atom.levels[ic].E - atom.levels[i].E) * Const.HC_CM 117 | deltaEj = (atom.levels[ic].E - atom.levels[j].E) * Const.HC_CM 118 | E_Rydberg = Const.ERydberg / (1.0 + Const.MElectron 119 | / (atom.element.mass * Const.Amu)) 120 | 121 | neff1 = Z * np.sqrt(E_Rydberg / deltaEi) 122 | neff2 = Z * np.sqrt(E_Rydberg / deltaEj) 123 | 124 | if nums[0] > nums[1]: 125 | neff1, neff2 = neff2, neff1 126 | 127 | if not (table.neff1[0] <= neff1 <= table.neff1[-1]): 128 | raise BarklemCrossSectionError('neff1 outside table.') 129 | if not (table.neff2[0] <= neff2 <= table.neff2[-1]): 130 | raise BarklemCrossSectionError('neff2 outside table.') 131 | 132 | 133 | result[0] = float(RectBivariateSpline(table.neff1, table.neff2, 134 | table.cross)(neff1, neff2)) 135 | result[1] = float(RectBivariateSpline(table.neff1, table.neff2, 136 | table.alpha)(neff1, neff2)) 137 | 138 | reducedMass = Const.Amu / (1.0 / PeriodicTable[1].mass + 1.0 / atom.element.mass) 139 | meanVel = np.sqrt(8.0 * Const.KBoltzmann / (np.pi * reducedMass)) 140 | sigma = result[0] 141 | alpha = result[1] 142 | crossSection = sigma * Const.RBohr**2 * (meanVel / 1.0e4)**(-alpha) 143 | 144 | # NOTE(cmo): This is w/N/T^(0.5*(1.0-alpha)) (eq 3 without temperature contrib, multiplied by 2 for half-width) 145 | result[0] = 2.0 * ((4.0 / np.pi)**(alpha / 2.0) 146 | * gamma(2.0 - alpha / 2.0) * meanVel * crossSection) 147 | 148 | # Use Unsold for Helium contribution 149 | result[2] = 1.0 150 | 151 | return result 152 | -------------------------------------------------------------------------------- /lightweaver/zeeman.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from fractions import Fraction 3 | from typing import TYPE_CHECKING, Iterator, Optional, cast 4 | 5 | import numpy as np 6 | 7 | if TYPE_CHECKING: 8 | from .atomic_model import AtomicLine 9 | 10 | def fraction_range(start: Fraction, stop: Fraction, 11 | step: Fraction=Fraction(1,1)) -> Iterator[Fraction]: 12 | ''' 13 | Works like range, but with Fractions. Does no checking, so best to make 14 | sure the range you're asking for is sane and divides down properly. 15 | ''' 16 | while start < stop: 17 | yield start 18 | start += step 19 | 20 | @dataclass 21 | class ZeemanComponents: 22 | ''' 23 | Storage for communicating the Zeeman components between functions, also 24 | shared with the backend, giving a slightly tighter contract than usual: 25 | all arrays must be contiguous and alpha must be of dtype np.int32. 26 | ''' 27 | alpha: np.ndarray 28 | strength: np.ndarray 29 | shift: np.ndarray 30 | 31 | def zeeman_strength(Ju: Fraction, Mu: Fraction, Jl: Fraction, Ml: Fraction) -> float: 32 | ''' 33 | Computes the strength of a Zeeman component, following del Toro Iniesta 34 | (p. 137) albeit larger by a factor of 2 which is corrected by 35 | normalisation. 36 | Takes J upper and lower (u and l respectively), and M upper and lower. 37 | ''' 38 | alpha = int(Ml - Mu) 39 | dJ = int(Ju - Jl) 40 | 41 | # These parameters are x2 those in del Toro Iniesta (p. 137), but we 42 | # normalise after the fact, so it's fine 43 | 44 | if dJ == 0: # jMin = ju = jl 45 | if alpha == 0: # pi trainsitions 46 | s = 2.0 * Mu**2 47 | elif alpha == -1: # sigma_b transitions 48 | s = (Ju + Mu) * (Ju - Mu + 1.0) 49 | elif alpha == 1: # sigma_r transitions 50 | s = (Ju - Mu) * (Ju + Mu + 1.0) 51 | elif dJ == 1: # jMin = jl, Mi = Ml 52 | if alpha == 0: # pi trainsitions 53 | s = 2.0 * ((Jl + 1)**2 - Ml**2) 54 | elif alpha == -1: # sigma_b transitions 55 | s = (Jl + Ml + 1) * (Jl + Ml + 2.0) 56 | elif alpha == 1: # sigma_r transitions 57 | s = (Jl - Ml + 1.0) * (Jl - Ml + 2.0) 58 | elif dJ == -1: # jMin = ju, Mi = Mu 59 | if alpha == 0: # pi trainsitions 60 | s = 2.0 * ((Ju + 1)**2 - Mu**2) 61 | elif alpha == -1: # sigma_b transitions 62 | s = (Ju - Mu + 1) * (Ju - Mu + 2.0) 63 | elif alpha == 1: # sigma_r transitions 64 | s = (Ju + Mu + 1.0) * (Ju + Mu + 2.0) 65 | else: 66 | raise ValueError('Invalid dJ: %d' % dJ) 67 | 68 | return float(s) 69 | 70 | def lande_factor(J: Fraction, L: int, S: Fraction) -> float: 71 | ''' 72 | Computes the Lande g-factor for an atomic level from the J, L, and S 73 | quantum numbers. 74 | ''' 75 | if J == 0.0: 76 | return 0.0 77 | return float(1.5 + (S * (S + 1.0) - L * (L + 1)) / (2.0 * J * (J + 1.0))) 78 | 79 | def effective_lande(line: 'AtomicLine'): 80 | ''' 81 | Computes the effective Lande g-factor for an atomic line. 82 | ''' 83 | if line.gLandeEff is not None: 84 | return line.gLandeEff 85 | 86 | i = line.iLevel 87 | j = line.jLevel 88 | if any(x is None for x in [i.J, i.L, i.S, j.J, j.L, j.S]): 89 | raise ValueError(('Cannot compute gLandeEff as gLandeEff not set and some of ' 90 | 'J, L and S None for line %s') % repr(line)) 91 | gL = lande_factor(i.J, i.L, i.S) # type: ignore 92 | gU = lande_factor(j.J, j.L, j.S) # type: ignore 93 | 94 | return 0.5 * (gU + gL) + \ 95 | 0.25 * (gU - gL) * (j.J * (j.J + 1.0) - i.J * (i.J + 1.0)) # type: ignore 96 | 97 | def compute_zeeman_components(line: 'AtomicLine') -> Optional[ZeemanComponents]: 98 | ''' 99 | Computes, if possible, the set of Zeeman components for an atomic line. 100 | 101 | If gLandeEff is specified on the line, then basic three-component Zeeman 102 | splitting will be computed directly. 103 | Otherwise, if both the lower and upper levels of the line support 104 | LS-coupling (i.e. J, L, and S all specified, and J <= L + S), then the 105 | LS-coupling formalism is applied to compute the components of "anomalous" 106 | Zeeman splitting. 107 | If neither of these cases are fulfilled, then None is returned. 108 | 109 | Parameters 110 | ---------- 111 | line : AtomicLine 112 | The line to attempt to compute the Zeeman components from. 113 | 114 | Returns 115 | ------- 116 | components : ZeemanComponents or None 117 | The Zeeman splitting components, if possible. 118 | ''' 119 | # NOTE(cmo): Just do basic three-component Zeeman splitting if an effective 120 | # Lande g-factor is specified on the line. 121 | if line.gLandeEff is not None: 122 | alpha = np.array([-1, 0, 1], dtype=np.int32) 123 | strength = np.ones(3) 124 | shift = alpha * line.gLandeEff 125 | return ZeemanComponents(alpha, strength, shift) 126 | 127 | # NOTE(cmo): Do LS coupling ("anomalous" Zeeman splitting) 128 | if line.iLevel.lsCoupling and line.jLevel.lsCoupling: 129 | # Mypy... you're a pain sometimes... (even if you are technically correct) 130 | Jl = cast(Fraction, line.iLevel.J) 131 | Ll = cast(int, line.iLevel.L) 132 | Sl = cast(Fraction, line.iLevel.S) 133 | Ju = cast(Fraction, line.jLevel.J) 134 | Lu = cast(int, line.jLevel.L) 135 | Su = cast(Fraction, line.jLevel.S) 136 | 137 | gLl = lande_factor(Jl, Ll, Sl) 138 | gLu = lande_factor(Ju, Lu, Su) 139 | alpha = [] 140 | strength = [] 141 | shift = [] 142 | norm = np.zeros(3) 143 | 144 | for ml in fraction_range(-Jl, Jl+1): 145 | for mu in fraction_range(-Ju, Ju+1): 146 | if abs(ml - mu) <= 1.0: 147 | alpha.append(int(ml - mu)) 148 | shift.append(gLl*ml - gLu*mu) 149 | strength.append(zeeman_strength(Ju, mu, Jl, ml)) 150 | norm[alpha[-1]+1] += strength[-1] 151 | alpha = np.array(alpha, dtype=np.int32) 152 | strength = np.array(strength) 153 | shift = np.array(shift) 154 | strength /= norm[alpha + 1] 155 | 156 | return ZeemanComponents(alpha, strength, shift) 157 | return None 158 | -------------------------------------------------------------------------------- /Source/ThreadStorage.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CMO_THREAD_STORAGE_HPP 2 | #define CMO_THREAD_STORAGE_HPP 3 | 4 | #include "Constants.hpp" 5 | #include "CmoArray.hpp" 6 | #include "JasPP.hpp" 7 | #include "LwInternal.hpp" 8 | #include "LwTransition.hpp" 9 | #include "LwAtom.hpp" 10 | #include "TaskScheduler.h" 11 | #include 12 | #include 13 | 14 | struct DepthData; 15 | struct Context; 16 | struct Atmosphere; 17 | struct Background; 18 | struct Spectrum; 19 | 20 | namespace LwInternal 21 | { 22 | 23 | struct PerTransFns 24 | { 25 | AllocPerTransScratch alloc_per; 26 | FreePerTransScratch free_per; 27 | }; 28 | 29 | struct PerAtomFns 30 | { 31 | AllocPerAtomScratch alloc_per; 32 | FreePerAtomScratch free_per; 33 | }; 34 | 35 | struct PerAtomTransFns 36 | { 37 | PerAtomFns perAtom; 38 | PerTransFns perTrans; 39 | }; 40 | 41 | struct TransitionStorage 42 | { 43 | F64Arr Rij; 44 | F64Arr Rji; 45 | Transition trans; 46 | FreePerTransScratch free_method_scratch; 47 | 48 | TransitionStorage() = default; 49 | TransitionStorage(const TransitionStorage&) = delete; 50 | TransitionStorage(TransitionStorage&&) = delete; 51 | inline TransitionStorage& operator=(const TransitionStorage&) = delete; 52 | inline TransitionStorage& operator=(TransitionStorage&&) = delete; 53 | ~TransitionStorage() 54 | { 55 | if (free_method_scratch) 56 | free_method_scratch(&trans); 57 | } 58 | }; 59 | 60 | struct TransitionStorageFactory 61 | { 62 | Transition* trans; 63 | bool detailedStatic; 64 | std::vector> tStorage; 65 | PerTransFns methodFns; 66 | TransitionStorageFactory(Transition* t, PerTransFns perFns); 67 | Transition* copy_transition(); 68 | void accumulate_rates(); 69 | void accumulate_prd_rates(); 70 | }; 71 | 72 | struct AtomStorage 73 | { 74 | F64Arr3D Gamma; 75 | Atom atom; 76 | FreePerAtomScratch free_method_scratch; 77 | 78 | AtomStorage() = default; 79 | AtomStorage(const AtomStorage&) = delete; 80 | AtomStorage(AtomStorage&&) = delete; 81 | inline AtomStorage& operator=(const AtomStorage&) = delete; 82 | inline AtomStorage& operator=(AtomStorage&&) = delete; 83 | ~AtomStorage() 84 | { 85 | if (free_method_scratch) 86 | free_method_scratch(&atom); 87 | } 88 | }; 89 | 90 | struct AtomStorageFactory 91 | { 92 | Atom* atom; 93 | bool detailedStatic; 94 | bool wlaGijStorage; 95 | bool defaultPerAtomStorage; 96 | int fsWidth; 97 | std::vector> aStorage; 98 | std::vector tStorage; 99 | PerAtomFns methodFns; 100 | AtomStorageFactory(Atom* a, bool detail, bool wlaStorage, 101 | bool defaultPerAtomStorage, 102 | int fsWidth, PerAtomTransFns perFns); 103 | Atom* copy_atom(); 104 | void accumulate_Gamma(); 105 | void accumulate_Gamma_rates(); 106 | void accumulate_prd_rates(); 107 | void accumulate_rates(); 108 | }; 109 | 110 | struct IntensityCoreStorage 111 | { 112 | F64Arr I; 113 | F64Arr S; 114 | F64Arr JDag; 115 | F64Arr chiTot; 116 | F64Arr etaTot; 117 | F64Arr Uji; 118 | F64Arr Vij; 119 | F64Arr Vji; 120 | F64Arr Ieff; 121 | F64Arr PsiStar; 122 | F64Arr2D JRest; 123 | std::vector activeAtoms; 124 | std::vector detailedAtoms; 125 | IntensityCoreData core; 126 | FormalData formal; 127 | 128 | IntensityCoreStorage(int Nspace, int NhPrd) 129 | : I(F64Arr(0.0, Nspace)), 130 | S(F64Arr(0.0, Nspace)), 131 | JDag(F64Arr(0.0, Nspace)), 132 | chiTot(F64Arr(0.0, Nspace)), 133 | etaTot(F64Arr(0.0, Nspace)), 134 | Uji(F64Arr(0.0, Nspace)), 135 | Vij(F64Arr(0.0, Nspace)), 136 | Vji(F64Arr(0.0, Nspace)), 137 | Ieff(F64Arr(0.0, Nspace)), 138 | PsiStar(F64Arr(0.0, Nspace)), 139 | JRest() 140 | { 141 | if (NhPrd > 0) 142 | { 143 | JRest = F64Arr2D(NhPrd, Nspace); 144 | } 145 | } 146 | }; 147 | 148 | struct IntensityCoreFactory 149 | { 150 | Atmosphere* atmos; 151 | Spectrum* spect; 152 | Background* background; 153 | DepthData* depthData; 154 | int fsWidth; 155 | LwFsFn formal_solver; 156 | InterpFn interp; 157 | std::vector activeAtoms; 158 | std::vector detailedAtoms; 159 | std::vector> arrayStorage; 160 | 161 | IntensityCoreFactory() : atmos(nullptr), 162 | spect(nullptr), 163 | background(nullptr), 164 | depthData(nullptr), 165 | fsWidth(1), 166 | formal_solver(), 167 | interp(), 168 | activeAtoms(), 169 | detailedAtoms(), 170 | arrayStorage() 171 | {} 172 | 173 | void initialise(Context* ctx); 174 | IntensityCoreData* new_intensity_core(); 175 | IntensityCoreData* single_thread_intensity_core(); 176 | void accumulate_JRest(); 177 | void accumulate_Gamma_rates(); 178 | void accumulate_prd_rates(bool includeDetailedAtoms); 179 | void accumulate_Gamma_rates_parallel(Context& ctx); 180 | void clear(); 181 | }; 182 | 183 | struct IterationCores 184 | { 185 | IntensityCoreFactory* factory; 186 | std::vector cores; 187 | std::vector indices; 188 | IterationCores() : factory(nullptr), 189 | cores() 190 | {}; 191 | ~IterationCores(); 192 | 193 | void initialise(IntensityCoreFactory* fac, int Nthreads); 194 | void accumulate_Gamma_rates(); 195 | void accumulate_prd_rates(bool includeDetailedAtoms); 196 | void accumulate_Gamma_rates_parallel(Context& ctx); 197 | void clear(); 198 | }; 199 | 200 | 201 | struct ThreadData 202 | { 203 | IntensityCoreFactory threadDataFactory; 204 | IterationCores intensityCores; 205 | enki::TaskScheduler sched; 206 | std::function clear_global_scratch; 207 | 208 | ThreadData() : threadDataFactory(), 209 | intensityCores(), 210 | sched() 211 | {} 212 | 213 | 214 | void initialise(Context* ctx); 215 | void clear(Context* ctx); 216 | 217 | ~ThreadData() 218 | { 219 | sched.WaitforAllAndShutdown(); 220 | if (clear_global_scratch) 221 | clear_global_scratch(); 222 | } 223 | ThreadData(const ThreadData&) = delete; 224 | ThreadData(ThreadData&&) = delete; 225 | inline ThreadData& operator=(const ThreadData&) = delete; 226 | inline ThreadData& operator=(ThreadData&&) = delete; 227 | }; 228 | 229 | } 230 | #else 231 | #endif -------------------------------------------------------------------------------- /Source/FastBackground.cpp: -------------------------------------------------------------------------------- 1 | #include "FastBackground.hpp" 2 | #include "Background.hpp" 3 | #include "Utils.hpp" 4 | 5 | 6 | void bf_opacities(BackgroundData* bd, const std::vector& atoms, 7 | Atmosphere* atmos, int laStart, int laEnd) 8 | { 9 | if (atoms.size() == 0) 10 | return; 11 | JasUnpack((*bd), wavelength, chi, eta); 12 | 13 | if (laStart < 0 && laEnd < 0) 14 | { 15 | laStart = 0; 16 | laEnd = wavelength.shape(0); 17 | } 18 | 19 | namespace C = Constants; 20 | 21 | constexpr f64 hc_k = C::HC / (C::KBoltzmann * C::NM_TO_M); 22 | constexpr f64 twohc = (2.0 * C::HC) / cube(C::NM_TO_M); 23 | 24 | for (int a = 0; a < atoms.size(); ++a) 25 | { 26 | auto& atom = atoms[a]; 27 | for (int c = 0; c < atom.continua.size(); ++c) 28 | { 29 | auto& cont = atom.continua[c]; 30 | int cLaStart = cont.laStart; 31 | int cLaEnd = cont.laEnd; 32 | if (cLaStart < laStart) 33 | cLaStart = laStart; 34 | if (cLaEnd > laEnd) 35 | cLaEnd = laEnd; 36 | 37 | if (cLaStart >= cLaEnd) 38 | continue; 39 | 40 | for (int la = cLaStart; la < cLaEnd; ++la) 41 | { 42 | f64 alpha = cont.alpha(la); 43 | f64 hc_kla = hc_k / wavelength(la); 44 | f64 twohnu3_c2 = twohc / cube(wavelength(la)); 45 | 46 | for (int k = 0; k < atmos->Nspace; ++k) 47 | { 48 | f64 expla = exp(-hc_kla / atmos->temperature(k)); 49 | f64 gijk = atom.nStar(cont.i, k) / atom.nStar(cont.j, k) * expla; 50 | chi(la, k) += alpha * (1.0 - expla) * atom.n(cont.i, k); 51 | eta(la, k) += twohnu3_c2 * gijk * alpha * atom.n(cont.j, k); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | 59 | void rayleigh_scattering(BackgroundData* bd, const std::vector& atoms, 60 | Atmosphere* atmos, int laStart, int laEnd) 61 | { 62 | if (atoms.size() == 0) 63 | return; 64 | JasUnpack((*bd), wavelength, scatt); 65 | 66 | if (laStart < 0 && laEnd < 0) 67 | { 68 | laStart = 0; 69 | laEnd = wavelength.shape(0); 70 | } 71 | namespace C = Constants; 72 | 73 | constexpr f64 c = 2.0 * C::Pi * (C::QElectron / C::Epsilon0) 74 | * C::QElectron / C::MElectron / C::CLight; 75 | const f64 sigmaE = 8.0 * C::Pi / 3.0 * std::pow(C::QElectron / (sqrt(4.0 * C::Pi * C::Epsilon0) * (sqrt(C::MElectron) * C::CLight)), 4.0); 76 | 77 | for (int a = 0; a < atoms.size(); ++a) 78 | { 79 | auto& atom = atoms[a]; 80 | for (int l = 0; l < atom.resonanceScatterers.size(); ++l) 81 | { 82 | auto& line = atom.resonanceScatterers[l]; 83 | if (wavelength(laEnd-1) <= line.lambdaMax) 84 | continue; 85 | 86 | for (int la = laStart; la < laEnd; ++la) 87 | { 88 | // NOTE(cmo): This is anticipating small wavelength ranges so a 89 | // linear search is perfectly fine. 90 | if (wavelength(la) <= line.lambdaMax) 91 | continue; 92 | f64 lambda2 = 1.0 / (square(wavelength(la) / line.lambda0) - 1.0); 93 | f64 f = line.Aji * line.gRatio * square(line.lambda0 * C::NM_TO_M) / c; 94 | f64 sigmaRayleigh = f * square(lambda2) * sigmaE; 95 | for (int k = 0; k < atmos->Nspace; ++k) 96 | scatt(la, k) += sigmaRayleigh * atom.n(0, k); 97 | } 98 | } 99 | } 100 | } 101 | 102 | void FastBackgroundContext::basic_background(BackgroundData* bd, Atmosphere* atmos) 103 | { 104 | if (Nthreads <= 1) 105 | { 106 | ::basic_background(bd, atmos); 107 | } 108 | else 109 | { 110 | struct BasicBackgroundData 111 | { 112 | BackgroundData* bd; 113 | Atmosphere* atmos; 114 | }; 115 | 116 | bd->chi.fill(0.0); 117 | bd->eta.fill(0.0); 118 | bd->scatt.fill(0.0); 119 | 120 | auto background_handler = [](void* data, enki::TaskScheduler* s, 121 | enki::TaskSetPartition p, u32 threadId) 122 | { 123 | BasicBackgroundData* args = (BasicBackgroundData*)data; 124 | ::basic_background(args->bd, args->atmos, p.start, p.end); 125 | }; 126 | 127 | BasicBackgroundData args{bd, atmos}; 128 | 129 | { 130 | LwTaskSet bgOpacities((void*)&args, &sched, bd->wavelength.shape(0), 131 | 20, background_handler); 132 | sched.AddTaskSetToPipe(&bgOpacities); 133 | sched.WaitforTask(&bgOpacities); 134 | } 135 | } 136 | } 137 | 138 | void FastBackgroundContext::bf_opacities(BackgroundData* bd, 139 | std::vector* atoms, 140 | Atmosphere* atmos) 141 | { 142 | if (Nthreads <= 1) 143 | { 144 | ::bf_opacities(bd, *atoms, atmos, -1, -1); 145 | } 146 | else 147 | { 148 | struct BfData 149 | { 150 | BackgroundData* bd; 151 | std::vector* atoms; 152 | Atmosphere* atmos; 153 | }; 154 | 155 | auto bf_handler = [](void* data, enki::TaskScheduler* s, 156 | enki::TaskSetPartition p, u32 threadId) 157 | { 158 | BfData* args = (BfData*)data; 159 | ::bf_opacities(args->bd, *args->atoms, args->atmos, p.start, p.end); 160 | }; 161 | 162 | BfData args{bd, atoms, atmos}; 163 | 164 | { 165 | LwTaskSet bfOpacities((void*)&args, &sched, bd->wavelength.shape(0), 166 | 20, bf_handler); 167 | sched.AddTaskSetToPipe(&bfOpacities); 168 | sched.WaitforTask(&bfOpacities); 169 | } 170 | } 171 | } 172 | void FastBackgroundContext::rayleigh_scatter(BackgroundData* bd, 173 | std::vector* atoms, 174 | Atmosphere* atmos) 175 | { 176 | if (Nthreads <= 1) 177 | { 178 | ::rayleigh_scattering(bd, *atoms, atmos, -1, -1); 179 | } 180 | else 181 | { 182 | struct RayleighData 183 | { 184 | BackgroundData* bd; 185 | std::vector* atoms; 186 | Atmosphere* atmos; 187 | }; 188 | 189 | auto rayleigh_handler = [](void* data, enki::TaskScheduler* s, 190 | enki::TaskSetPartition p, u32 threadId) 191 | { 192 | RayleighData* args = (RayleighData*)data; 193 | ::rayleigh_scattering(args->bd, *args->atoms, args->atmos, p.start, p.end); 194 | }; 195 | 196 | RayleighData args{bd, atoms, atmos}; 197 | 198 | { 199 | LwTaskSet rayleighScatter((void*)&args, &sched, bd->wavelength.shape(0), 200 | 40, rayleigh_handler); 201 | sched.AddTaskSetToPipe(&rayleighScatter); 202 | sched.WaitforTask(&rayleighScatter); 203 | } 204 | } 205 | } --------------------------------------------------------------------------------