File: test_activation.py

package info (click to toggle)
python-periodictable 2.0.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,068 kB
  • sloc: python: 13,338; makefile: 103; sh: 92; javascript: 7
file content (134 lines) | stat: -rw-r--r-- 6,227 bytes parent folder | download
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
import numpy as np

import periodictable as pt
from periodictable.activation import Sample, ActivationEnvironment
from periodictable.activation import IAEA1987_isotopic_abundance, table_abundance

def test():
    # This is not a very complete test of the activation calculator.
    # Mostly just a smoke test to see that things run and produce the
    # same answers as before.  The target values have been checked
    # against the NCNR internal activation calculator spreadsheet.  The
    # values herein differ slightly from the spreadsheet since we are using
    # different tables for materials and different precision on our
    # constants. There has also been some small corrections to the
    # formulas over time.

    def _get_Au_activity(fluence=1e5):
        sample = Sample('Au', mass=1)
        env = ActivationEnvironment(fluence=fluence)
        sample.calculate_activation(env, rest_times=[0])
        for product, activity in sample.activity.items():
            if str(product.daughter) ==  'Au-198':
                return activity[0]
        else:
            raise RuntimeError("missing activity from Au-198")

    # Activity scales linearly with fluence and mass
    act1 = _get_Au_activity(fluence=1e5)
    act2 = _get_Au_activity(fluence=1e8)
    #print(f"Au: {act1} x 1000 = {act2}")
    assert (act2 - 1000*act1) < 1e-8

    # Smoke test: does every element run to completion?
    formula = "".join(str(el) for el in pt.elements)
    # Use an enormous mass to force significant activation of rare isotopes
    mass, fluence, exposure = 1e15, 1e8, 10
    env = ActivationEnvironment(fluence=fluence, Cd_ratio=70, fast_ratio=50, location="BT-2")
    sample = Sample(formula, mass=mass)
    sample.calculate_activation(
        env, exposure=exposure, rest_times=(0, 1, 24),
        abundance=IAEA1987_isotopic_abundance,
        #abundance=table_abundance,
        )
    #sample.show_table(cutoff=0)

    sample = Sample('Co', mass=10)
    env = ActivationEnvironment(fluence=1e8)
    sample.calculate_activation(
        env, rest_times=[0, 1, 24],
        abundance=IAEA1987_isotopic_abundance,
        #abundance=table_abundance,
        )
    #sample.show_table(cutoff=0)
    # ACT_2N_X.xls for 10g Co at Io=1e8 for t = [0, 1, 24]
    # There are duplicate entries for Co-61 because there are different
    # activation paths for 59Co + n -> 61Co.
    # Results are limited to 3 digits because formulas use ln(2) = 0.693. There
    # is also precision loss on the calculation of differences in exponentials,
    # with exp(a) - exp(b) converted to exp(b)(exp(a-b) - 1) = exp(b)expm1(a-b)
    # in activation.py.
    # results = {
    #     "C0-60m+":  [5.08800989419543E+03,	9.69933141298983E+01,	2.69898447909379E-38],
    #     "Co-61":    [7.30937687202485E-09,	4.80260282859366E-09,	3.06331725449002E-13],
    #     "Co-60":    [1.55042700053951E-01,	1.55040373560730E-01,	1.54986873850802E-01],
    #     "Co-61":    [1.36469792582999E-09,	8.96670432174800E-10,	5.71936948464344E-14],
    # }
    # Results from R2.0.0-pre
    results = {
        "Co-60m+":	[5.08746786932552e+03,	9.69014499692868e+01,	2.64477047705988e-38],
        "Co-61":	[7.30866736559643e-09,	4.80170831653643e-09,	3.05646958051663e-13],
        "Co-60":	[1.55056574647797e-01,	1.55054247452235e-01,	1.55000731593431e-01],
        "Co-61":	[1.36478223029061e-09,	8.96645839472098e-10,	5.70749106813738e-14],
    }
    #print(list(sample.activity.keys()))
    #print(" ".join(k for k in dir(list(sample.activity.keys())[0]) if k[0] != '_'))
    #print(list(sample.activity.keys())[0].__dict__)
    for product, activity in sample.activity.items():
        #print(product)
        #print(dir(product))
        # Uncomment to show new table values
        activity_str = ",\t".join(f"{Ia:.14E}" for Ia in activity)
        #print(f'        "{product.daughter}":\t[{activity_str}],')
        assert product.daughter in results, f"Missing {product.daughter}"
        # TODO: include duplicate decay paths in test, or identical daughters
        # Test that results haven't changed since last update
        if product.daughter != "Co-61":
            assert np.allclose(results[product.daughter], activity, atol=0, rtol=1e-12)

    # 129-I has a long half-life (16 My) so any combination of exposure
    # fluence and mass that causes significant activation will yield a product
    # that is radioactive for a very long time.
    sample = Sample('Te', mass=1e13)
    env = ActivationEnvironment(fluence=1e8)
    sample.calculate_activation(env, rest_times=[1,10,100])
    #sample.show_table(cutoff=0)
    target = 1e-5
    t_decay = sample.decay_time(target)
    #print(f"{t_decay=}")
    sample.calculate_activation(env, rest_times=[t_decay])
    total = sum(v[-1] for v in sample.activity.values())
    assert abs(total - target) < 1e-10, f"total activity {total} != {target}"

    # Al and Si daughters have short half-lives
    sample = Sample('AlSi', mass=1e3)
    env = ActivationEnvironment(fluence=1e8)
    sample.calculate_activation(env, rest_times=[100,200])
    #sample.show_table(cutoff=0)
    target = 1e-5
    t_decay = sample.decay_time(target)
    #print(f"{t_decay=}")
    sample.calculate_activation(env, rest_times=[t_decay])
    total = sum(v[-1] for v in sample.activity.values())
    assert abs(total - target) < 1e-10, f"total activity {total} != {target}"

    #print()
    if 0: # TODO: doesn't work for high fluence
        # Test high fluence
        sample = Sample('Al2O3', mass=1e3)
        sample = Sample('Al', mass=1e3)
        flow, scale = 1e16, 1e2
        rest_times = [0, 10]
        env = ActivationEnvironment(fluence=flow*scale)
        sample.calculate_activation(env, rest_times=rest_times)
        sample.show_table(cutoff=0)
        ahigh = list(sample.activity.values())
        env = ActivationEnvironment(fluence=flow)
        sample.calculate_activation(env, rest_times=rest_times)
        sample.show_table(cutoff=0)
        alow = np.asarray(list(sample.activity.values()))
        for alow_k, ahigh_k in zip(alow, ahigh):
            #print(f"{alow_k=} {ahigh_k=}")
            assert np.allclose(alow_k*scale, ahigh_k), f"scaling fails at {alow_k=} {ahigh_k=}"

test()