Coverage for yaptide/converter/converter/fluka/cards/scoring_card.py: 95%

87 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-07-01 12:55 +0000

1from dataclasses import dataclass, field 

2from typing import Optional, Union 

3 

4from converter.fluka.cards.card import Card 

5from converter.fluka.helper_parsers.detector_parser import CylinderDetector, MeshDetector 

6from converter.fluka.helper_parsers.scoring_parser import CustomFilter, ParticleFilter, Quantity, Scoring 

7 

8_DetectorType = type(Union[MeshDetector, CylinderDetector]) 

9 

10 

11@dataclass 

12class ScoringCardIndexCounter: 

13 """Class representing counter for Fluka scoring cards""" 

14 

15 usrbin_counter: int = 0 

16 

17 

18def handle_scoring_cards(output_unit: int, scoring: Scoring, counter: ScoringCardIndexCounter) -> str: 

19 """Creates Scoring cards""" 

20 output: list[str] = [] 

21 for quantity in scoring.quantities: 

22 usrbin_card = handle_usrbin_scoring(scoring.detector, quantity, output_unit, counter) 

23 output.append(usrbin_card) 

24 return '\n'.join(output).strip() 

25 

26 

27def short_name(name: str) -> str: 

28 """Creates short name for fluka card""" 

29 return name[:10] 

30 

31 

32def handle_usrbin_scoring(detector: _DetectorType, quantity: Quantity, output_unit, 

33 counter: ScoringCardIndexCounter) -> str: 

34 """Creates USRBIN card""" 

35 output: list[Card] = [] 

36 # temporary assumption 

37 # DOSE according to: 

38 # https://flukafiles.web.cern.ch/manual/chapters/particle_and_material_codes/particles_codes.html 

39 quantity_to_score = '' 

40 try_auxscore = False 

41 if quantity.keyword == 'Dose': 

42 quantity_to_score = 'DOSE' 

43 try_auxscore = True 

44 elif quantity.keyword == 'Fluence': 

45 if isinstance(quantity.scoring_filter, ParticleFilter): 

46 # Apply particle from filter if fluency is used 

47 quantity_to_score = quantity.scoring_filter.particle 

48 else: 

49 quantity_to_score = 'ALL-PART' 

50 if isinstance(quantity.scoring_filter, CustomFilter): 

51 try_auxscore = True 

52 

53 if not quantity_to_score: 

54 return f'* unable to create USRBIN card for {quantity.name[:20]}' 

55 

56 output_unit_in_fluka_convention = str(output_unit * -1) 

57 

58 output.extend(parse_detector(detector, quantity, quantity_to_score, output_unit_in_fluka_convention)) 

59 

60 counter.usrbin_counter += 1 

61 if try_auxscore and quantity.scoring_filter: 

62 # Add AUXSCORE card for custom filter 

63 auxscore_card = handle_auxscore_filter(quantity, counter.usrbin_counter, 'USRBIN') 

64 if auxscore_card: 

65 output.append(auxscore_card) 

66 

67 return '\n'.join([f'{card!s}' for card in output]) 

68 

69 

70def parse_detector(detector, quantity, quantity_to_score, output_unit_in_fluka_convention) -> list[Card]: 

71 """Creates USRBIN card""" 

72 if isinstance(detector, CylinderDetector): 

73 return _parse_cylinder_detector(detector, quantity, quantity_to_score, 

74 output_unit_in_fluka_convention) 

75 return _parse_mesh_detector(detector, quantity, quantity_to_score, output_unit_in_fluka_convention) 

76 

77 

78def _parse_mesh_detector(detector: MeshDetector, quantity: Quantity, quantity_to_score: str, 

79 output_unit_in_fluka_convention: str) -> list[Card]: 

80 """Creates USRBIN card for mesh detector""" 

81 first_card = Card(codewd='USRBIN') 

82 first_card.what = [ 

83 '10.0', quantity_to_score, output_unit_in_fluka_convention, detector.x_max, detector.y_max, detector.z_max 

84 ] 

85 first_card.sdum = short_name(quantity.name) 

86 

87 second_card = Card(codewd='USRBIN') 

88 second_card.what = [ 

89 detector.x_min, 

90 detector.y_min, 

91 detector.z_min, 

92 detector.x_bins, 

93 detector.y_bins, 

94 detector.z_bins, 

95 ] 

96 second_card.sdum = '&' 

97 

98 return [first_card, second_card] 

99 

100 

101def _parse_cylinder_detector(detector: CylinderDetector, quantity: Quantity, quantity_to_score: str, 

102 output_unit_in_fluka_convention: str) -> list[Card]: 

103 """Creates USRBIN card for cylinder detector""" 

104 first_card = Card(codewd='USRBIN') 

105 first_card.what = [ 

106 '11.0', quantity_to_score, output_unit_in_fluka_convention, detector.r_max, detector.y, detector.z_max 

107 ] 

108 first_card.sdum = short_name(quantity.name) 

109 

110 second_card = Card(codewd='USRBIN') 

111 second_card.what = [ 

112 detector.r_min, 

113 detector.x, 

114 detector.z_min, 

115 detector.r_bins, 

116 detector.phi_bins, 

117 detector.z_bins, 

118 ] 

119 second_card.sdum = '&' 

120 

121 return [first_card, second_card] 

122 

123 

124def handle_auxscore_filter(quantity: Quantity, score_index: int, score_card_type: str = 'USRBIN') -> str: 

125 """Creates AUXSCORE card for previously created card""" 

126 filter_value: Optional[Union[int, str]] = parse_filter_value(quantity.scoring_filter) 

127 if filter_value is None: 

128 return '' 

129 auxscore = Card(codewd='AUXSCORE') 

130 auxscore.what = [ 

131 score_card_type, 

132 filter_value, 

133 '', 

134 score_index, 

135 score_index, 

136 1, 

137 ] 

138 auxscore.sdum = '' 

139 

140 return f'{auxscore!s}' 

141 

142 

143def parse_filter_value(scoring_filter: Union[CustomFilter, ParticleFilter]) -> Optional[Union[int, str]]: 

144 """Parses filter value from filter""" 

145 if isinstance(scoring_filter, ParticleFilter): 

146 return scoring_filter.particle 

147 if isinstance(scoring_filter, CustomFilter): 

148 scoring_filter: CustomFilter 

149 # According to: 

150 # https://flukafiles.web.cern.ch/manual/chapters/description_input/description_options/auxscore.html#auxscore 

151 # We are using -(Z*100 + A*100000) for custom filters to define filter 

152 # for particles with atomic number equal to Z and mass number equal to A 

153 return -(scoring_filter.z * 100 + scoring_filter.a * 100000) 

154 

155 return None 

156 

157 

158@dataclass 

159class ScoringsCard: 

160 """Class representing set of scoring cards in Fluka""" 

161 

162 data: list[Scoring] = field(default_factory=list) 

163 

164 def __str__(self) -> str: 

165 # each Scoring card consists of two cards; 

166 # the second one is continuation of data included in first 

167 

168 # temporary default for no symmetry 

169 result: list[str] = [] 

170 

171 default_output_unit = 21 

172 counter = ScoringCardIndexCounter() 

173 for scoring in self.data: 

174 scoring_cards = handle_scoring_cards(default_output_unit, scoring, counter) 

175 if scoring_cards: 

176 result.append(scoring_cards) 

177 default_output_unit += 1 

178 return '\n'.join(result).strip()