• 84.85% Rate
  • 196 Hits
  • 35 Missed
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 1x
  • 1x
  • 1x
  • 1x
  • 1x
  • 1x
  • 1x
  • 1x
  • 18x
  • 12x
  • 6x
  • 3x
  • 3x
  • 18x
  • 18x
  • 36x
  • 18x
  • 18x
  • 18x
  • 18x
  • 36x
  • 1x
  • 1x
  • 12x
  • 12x
  • 12x
  • 12x
  • 12x
  • 6x
  • 6x
  • 6x
  • 12x
  • 6x
  • 12x
  • 1x
  • 1x
  • 6x
  • 6x
  • 1x
  • 1x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 12x
  • 6x
  • 12x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 6x
  • 12x
  • 12x
  • 6x
  • 6x
  • 12x
  • 12x
  • 1x
  • 16x
  • 16x
  • 8x
  • 6x
  • 3x
  • 3x
  • 2x
  • 1x
  • 2x
  • 6x
  • 2x
  • 10x
  • 10x
  • 10x
  • 5x
  • 4x
  • 2x
  • 6x
  • 6x
  • 6x
  • 10x
  • 15x
  • 10x
  • 5x
  • 58x
  • 29x
  • 1x
  • 82x
  • 37x
  • 45x
  • 8x
  • 37x
  • 29x
  • 8x
  • 2x
  • 6x
  • 6x
  • 1x
  • 1x
  • 31x
  • 31x
  • 31x
  • 31x
  • 31x
  • 54x
  • 54x
  • 27x
  • 27x
  • 27x
  • 27x
  • 4x
  • 4x
  • 8x
  • 4x
  • 8x
  • 4x
  • 8x
  • 31x
  • 4x
  • 4x
  • 31x
  • 31x
  • 1x
  • 1x
  • 1x
  • 1x
  • 60x
  • 31x
  • 13x
  • 29x
  • 16x
  • 1x
  • 1x
  • 15x
  • 7x
  • 13x
  • 13x
  • 12x
  • 16x
  • 16x
  • 32x
  • 32x
  • 13x
  • 18x
  • 48x
  • 30x
  • 27x
  • 6x
  • 21x
  • 18x
  • 18x
  • 18x
  • 6x
  • 6x
  • 6x
  • 12x
  • 12x
  • 6x
  • 6x
  • 6x
  • 12x
  • 6x
  • 6x
  • 6x
  • 6x
  • 12x
  • 18x
  • 1x
  • 1x
  • 1x
  • 1x
  • -------------------------------------------------------------------------------
  • -- Open sky muon flux models for PUMAS
  • -- Author: Valentin Niess
  • -- License: GNU LGPL-3.0
  • -------------------------------------------------------------------------------
  • local ffi = require('ffi')
  • local lfs = require('lfs')
  • local clib = require('pumas.clib')
  • local constants = require('pumas.constants')
  • local coordinates = require('pumas.coordinates')
  • local error = require('pumas.error')
  • local metatype = require('pumas.metatype')
  • local flux = {}
  • -------------------------------------------------------------------------------
  • -- Flux scaling according to a constant charge ratio
  • -------------------------------------------------------------------------------
  • local function ChargeRatio (charge_ratio)
  • return function (charge)
  • if charge == nil then
  • return 1
  • elseif charge < 0 then
  • return 1 / (1 + charge_ratio)
  • else
  • return charge_ratio / (1 + charge_ratio)
  • end
  • end
  • end
  • -------------------------------------------------------------------------------
  • -- Gaisser muon flux model
  • --
  • -- Ref: Particle Data Group (Cosmic Rays)
  • -------------------------------------------------------------------------------
  • local function GaisserFlux (normalisation, gamma, charge_ratio)
  • local ratio = ChargeRatio(charge_ratio)
  • return function (kinetic_energy, cos_theta, charge)
  • if cos_theta < 0 then return 0 end
  • local Emu = kinetic_energy + constants.MUON_MASS
  • local ec = 1.1 * Emu * cos_theta
  • local rpi = 1 + ec / 115
  • local rK = 1 + ec / 850
  • return normalisation * math.pow(Emu, -gamma) *
  • (1 / rpi + 0.054 / rK) * ratio(charge)
  • end
  • end
  • -------------------------------------------------------------------------------
  • -- Volkova's parameterization of cos(theta*)
  • -------------------------------------------------------------------------------
  • local cos_theta_star
  • do
  • local p = { 0.102573, -0.068287, 0.958633, 0.0407253, 0.817285 }
  • function cos_theta_star (cos_theta)
  • local cs2 = (cos_theta * cos_theta + p[1] * p[1] +
  • p[2] * math.pow(cos_theta, p[3]) +
  • p[4] * math.pow(cos_theta, p[5])) /
  • (1 + p[1] * p[1] + p[2] + p[4])
  • return (cs2 > 0) and math.sqrt(cs2) or 0
  • end
  • end
  • -------------------------------------------------------------------------------
  • -- GCCLY flux model
  • --
  • -- Ref: https://arxiv.org/abs/1509.06176
  • -------------------------------------------------------------------------------
  • local function GcclyFlux (normalisation, gamma, charge_ratio)
  • local gaisser = GaisserFlux(normalisation, gamma, charge_ratio)
  • return function (kinetic_energy, cos_theta, charge)
  • local cs = cos_theta_star(cos_theta)
  • if cs < 0 then return 0 end
  • local Emu = kinetic_energy + constants.MUON_MASS
  • return math.pow(1 + 3.64 / (Emu * math.pow(cs, 1.29)), -gamma) *
  • gaisser(kinetic_energy, cs, charge)
  • end
  • end
  • -------------------------------------------------------------------------------
  • -- Chirkin's parameterization of average mass overburden as function of
  • -- x=cos(theta), in g/cm^2
  • -------------------------------------------------------------------------------
  • local mass_overburden
  • do
  • local p = { -0.017326, 0.114236, 1.15043, 0.0200854, 1.16714 }
  • function mass_overburden (x)
  • return 1E+02 / (p[1] + p[2] * math.pow(x, p[3]) +
  • p[4] * math.pow((1 - x * x), p[5]))
  • end
  • end
  • -------------------------------------------------------------------------------
  • -- Chirkin's parameterization of average muon path length as function of
  • -- x=cos(theta), in m
  • -------------------------------------------------------------------------------
  • local path_length
  • do
  • local p = { 1.3144, 50.2813, 1.33545, 0.252313, 41.0344 }
  • function path_length (x)
  • return 1E+06 / (p[1] + p[2] * math.pow(x, p[3]) +
  • p[4] * math.pow((1 - x * x), p[5]))
  • end
  • end
  • -------------------------------------------------------------------------------
  • -- Chirkin flux model
  • --
  • -- Ref: https://arxiv.org/abs/hep-ph/0407078
  • -------------------------------------------------------------------------------
  • local function ChirkinFlux (normalisation, gamma, charge_ratio)
  • local gaisser = GaisserFlux(normalisation, gamma, charge_ratio)
  • local a = 2.62E-03
  • local b = 3.05E-06
  • local astar = 0.487E-03
  • local bstar = 8.766E-06
  • local X0 = 114.8
  • local c0 = astar - bstar * a / b
  • c0 = c0 * c0
  • local c1 = (astar * astar - c0) / a
  • local c2 = bstar * bstar / b
  • local d1 = 0.054
  • return function (kinetic_energy, cos_theta, charge)
  • local cs = cos_theta_star(cos_theta)
  • if cs < 0 then return 0 end
  • -- Calculate the effective initial energy, EI, and its derivative
  • local EI, dEIdEf
  • do
  • local X = mass_overburden(cos_theta) - X0
  • local ebx = math.exp(b * X)
  • local Ef = kinetic_energy + constants.MUON_MASS
  • local Ei = ((a + b * Ef) * ebx - a) / b
  • local sE = 0.5 * c2 * (Ei * Ei - Ef * Ef) + c1 * (Ei - Ef) +
  • c0 / b * math.log((a + b * Ei) / (a + b * Ef))
  • local dXf = astar + bstar * Ef
  • local dXi = astar + bstar * Ei
  • local dsEdEf = ebx * dXi * dXi / (a + b * Ei) -
  • dXf * dXf / (a + b * Ef)
  • local d0 = 1.1 * cs / 115
  • local d2 = 1.1 * cs / 850
  • local d01 = 1 / (1 + d0 * Ei)
  • local d21 = 1 / (1 + d2 * Ei)
  • local num1 = d0 * d01 * d01
  • local num2 = d1 * d2 * d21 * d21
  • local num = num1 + num2
  • local iden = 1 / (d01 + d1 * d21)
  • local fr = -gamma / Ei - num * iden
  • local dfrdEf = gamma / (Ei * Ei) +
  • (num * num * iden + 2 * (d0 * num1 * d01 +
  • d2 * num2 * d21)) * iden
  • EI = Ei + 0.5 * sE * fr
  • dEIdEf = math.abs(ebx * (1 + 0.5 * dfrdEf * sE) +
  • 0.5 * fr * dsEdEf)
  • end
  • -- Calculate the survival factor (W)
  • local d0 = path_length(cos_theta)
  • local ctau = 658.65
  • local W = math.exp(-d0 / ctau * constants.MUON_MASS / EI)
  • -- Return the modified Gaisser's flux
  • return dEIdEf * W * gaisser(EI, cs, charge)
  • end
  • end
  • -------------------------------------------------------------------------------
  • -- Muon flux metatype
  • -------------------------------------------------------------------------------
  • local MuonFlux = {}
  • do
  • local function sample (self, state)
  • if metatype(self) ~= 'MuonFlux' then
  • error.raise{fname = 'sample', argnum = 1,
  • expected = 'a MuonFlux table', got = metatype.a(self)}
  • elseif metatype(state) ~= 'State' then
  • error.raise{fname = 'sample', argnum = 2,
  • expected = 'a State table', got = metatype.a(state)}
  • end
  • local altitude, cos_theta
  • if self._axis == 'vertical' then
  • self._position:set(state.position)
  • altitude = self._position.altitude - self._origin
  • if rawget(self, '_altitude') and
  • (math.abs(self._altitude - altitude) > 1E-03) then
  • return false
  • end
  • local frame = coordinates.LocalFrame(self._position)
  • self._direction:set(state.direction):transform(frame)
  • cos_theta = -self._direction.z
  • else
  • altitude = (state.position[0] - self._origin.x) * self._axis.x +
  • (state.position[1] - self._origin.y) * self._axis.y +
  • (state.position[2] - self._origin.z) * self._axis.z
  • if rawget(self, '_altitude') and
  • (math.abs(altitude - self._altitude) > 1E-03) then
  • return false
  • end
  • cos_theta = -state.direction[0] * self._axis.x -
  • state.direction[1] * self._axis.y -
  • state.direction[2] * self._axis.z
  • end
  • local f = self._spectrum(
  • state.energy, cos_theta, state.charge, altitude)
  • state.weight = state.weight * f
  • return true, f
  • end
  • local function spectrum (self, energy, cos_theta, charge, altitude)
  • if metatype(self) ~= 'MuonFlux' then
  • error.raise{fname = 'spectrum', argnum = 1,
  • expected = 'a MuonFlux table', got = metatype.a(self)}
  • end
  • return self._spectrum(energy, cos_theta, charge, altitude)
  • end
  • function MuonFlux:__index (k)
  • if k == '__metatype' then
  • return 'MuonFlux'
  • elseif k == 'sample' then
  • return sample
  • elseif k == 'spectrum' then
  • return spectrum
  • elseif k == 'altitude' then
  • return rawget(self, '_altitude')
  • elseif k == 'model' then
  • return rawget(self, '_model')
  • else
  • error.raise{['type'] = 'MuonFlux', bad_member = k}
  • end
  • end
  • end
  • function MuonFlux.__newindex (_, k)
  • if (k == 'altitude') or (k == 'model') then
  • error.raise{['type'] = 'MuonFlux', not_mutable = k}
  • else
  • error.raise{['type'] = 'MuonFlux', bad_member = k}
  • end
  • end
  • -------------------------------------------------------------------------------
  • -- Muon flux constructor
  • -------------------------------------------------------------------------------
  • do
  • local raise_error = error.ErrorFunction{fname = 'MuonFlux'}
  • local function new (cls, options)
  • options = options or {}
  • local model = options.model or 'mceq'
  • local self = {_model = model}
  • -- Set the vertical axis and the origin of the model
  • local axis = options.axis or 'vertical'
  • if axis == 'vertical' then
  • self._position = coordinates.GeodeticPoint()
  • self._direction = coordinates.CartesianVector()
  • self._axis = axis
  • local origin = options.origin or 0
  • if type(origin) == 'number' then
  • self._origin = origin
  • else
  • raise_error{argname = 'origin', expected = 'a number',
  • got = metatype.a(origin)}
  • end
  • else
  • local mt = metatype(axis)
  • if mt == 'table' then
  • self._axis = coordinates.CartesianVector(axis)
  • else
  • self._axis = coordinates.CartesianVector():set(axis)
  • end
  • local origin = options.origin or {0, 0, 0}
  • mt = metatype(origin)
  • if mt == 'table' then
  • self._origin = coordinates.CartesianPoint(origin)
  • else
  • self._origin = coordinates.CartesianPoint():set(origin)
  • end
  • end
  • -- Set any default altitude
  • if options.altitude then
  • if type(options.altitude) == 'number' then
  • self._altitude = options.altitude
  • else
  • raise_error{argname = 'altitude', expected = 'a number',
  • got = metatype.a(options.altitude)}
  • end
  • end
  • local tag, data
  • if type(model) == 'string' then
  • if lfs.attributes(model, 'mode') == 'file' then
  • data = clib.pumas_flux_tabulation_load(model)
  • if data == nil then
  • raise_error{argname = 'model',
  • description = 'could not load flux tabulation from '..
  • model}
  • end
  • ffi.gc(data, ffi.C.free)
  • self._data = data
  • else
  • tag = model:lower()
  • end
  • elseif type(model) ~= 'function' then
  • raise_error{argname = 'model',
  • expected = 'a function or a string', got = metatype.a(model)}
  • end
  • if (tag == 'mceq') or (tag == nil) or data then
  • local normalisation = 1
  • for k, v in pairs(options) do
  • if k == 'normalisation' then
  • if type(v) == 'number' then
  • normalisation = v
  • else
  • raise_error{argname = 'normalisation',
  • expected = 'a number', got = metatype.a(v)}
  • end
  • elseif (k ~= 'altitude') and (k ~= 'axis') and
  • (k ~= 'model') and (k ~= 'origin')
  • then
  • raise_error{
  • argnum = 2, description = "unknown option '"..k..
  • "' for 'mceq' model"}
  • end
  • end
  • if tag or data then
  • if not data then
  • data = clib.pumas_flux_tabulation_data[0]
  • end
  • self._spectrum = function (
  • kinetic_energy, cos_theta, charge, altitude)
  • altitude = altitude or rawget(self, '_altitude') or 0
  • charge = charge or 0
  • return clib.pumas_flux_tabulation_get(data, kinetic_energy,
  • cos_theta, altitude, charge) * normalisation
  • end
  • else
  • self._spectrum = function (
  • kinetic_energy, cos_theta, charge, altitude)
  • altitude = altitude or rawget(self, '_altitude') or 0
  • return model(data, kinetic_energy, cos_theta, charge,
  • altitude) * normalisation
  • end
  • end
  • return setmetatable(self, cls)
  • end
  • local charge_ratio, gamma
  • local normalisation = 1
  • local function parse_args ()
  • for k, v in pairs(options) do
  • if k == 'charge_ratio' then charge_ratio = v
  • elseif k == 'gamma' then
  • gamma = v
  • elseif k == 'normalisation' then normalisation = v
  • elseif (k ~= 'altitude') and (k ~= 'axis') and (k ~= 'model')
  • and (k ~= 'origin')
  • then
  • raise_error{
  • argnum = 2,
  • description = "unknown option '"..k..
  • "' for '"..model.."' model"
  • }
  • end
  • end
  • charge_ratio = charge_ratio or 1.2766
  • -- Ref: CMS (https://arxiv.org/abs/1005.5332)
  • end
  • if tag == 'gaisser' then
  • parse_args()
  • gamma = gamma or 2.7
  • normalisation = normalisation * 1.4E+03
  • self._spectrum = GaisserFlux(normalisation, gamma, charge_ratio)
  • elseif tag == 'gccly' then
  • parse_args()
  • gamma = gamma or 2.7
  • normalisation = normalisation * 1.4E+03
  • self._spectrum = GcclyFlux(normalisation, gamma, charge_ratio)
  • elseif tag == 'chirkin' then
  • parse_args()
  • gamma = gamma or 2.715
  • normalisation = normalisation * 9.814E+02
  • self._spectrum = ChirkinFlux(normalisation, gamma, charge_ratio)
  • else
  • raise_error{
  • argnum = 1,
  • description = "'unknown flux model '"..model.."'"
  • }
  • end
  • return setmetatable(self, cls)
  • end
  • flux.MuonFlux = setmetatable(MuonFlux, {__call = new})
  • end
  • -------------------------------------------------------------------------------
  • -- Register the subpackage
  • -------------------------------------------------------------------------------
  • function flux.register_to (t)
  • t.MuonFlux = flux.MuonFlux
  • end
  • -------------------------------------------------------------------------------
  • -- Return the package
  • -------------------------------------------------------------------------------
  • return flux