xcoll 0.3.5__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. xcoll/__init__.py +12 -4
  2. xcoll/beam_elements/__init__.py +7 -5
  3. xcoll/beam_elements/absorber.py +41 -7
  4. xcoll/beam_elements/base.py +1161 -244
  5. xcoll/beam_elements/collimators_src/black_absorber.h +118 -0
  6. xcoll/beam_elements/collimators_src/black_crystal.h +111 -0
  7. xcoll/beam_elements/collimators_src/everest_block.h +40 -28
  8. xcoll/beam_elements/collimators_src/everest_collimator.h +129 -50
  9. xcoll/beam_elements/collimators_src/everest_crystal.h +217 -73
  10. xcoll/beam_elements/everest.py +60 -113
  11. xcoll/colldb.py +250 -750
  12. xcoll/general.py +2 -2
  13. xcoll/headers/checks.h +1 -1
  14. xcoll/headers/particle_states.h +2 -2
  15. xcoll/initial_distribution.py +195 -0
  16. xcoll/install.py +177 -0
  17. xcoll/interaction_record/__init__.py +1 -0
  18. xcoll/interaction_record/interaction_record.py +252 -0
  19. xcoll/interaction_record/interaction_record_src/interaction_record.h +98 -0
  20. xcoll/{impacts → interaction_record}/interaction_types.py +11 -4
  21. xcoll/line_tools.py +83 -0
  22. xcoll/lossmap.py +209 -0
  23. xcoll/manager.py +2 -937
  24. xcoll/rf_sweep.py +1 -1
  25. xcoll/scattering_routines/everest/amorphous.h +239 -0
  26. xcoll/scattering_routines/everest/channeling.h +245 -0
  27. xcoll/scattering_routines/everest/crystal_parameters.h +137 -0
  28. xcoll/scattering_routines/everest/everest.h +8 -30
  29. xcoll/scattering_routines/everest/everest.py +13 -10
  30. xcoll/scattering_routines/everest/jaw.h +27 -197
  31. xcoll/scattering_routines/everest/materials.py +2 -0
  32. xcoll/scattering_routines/everest/multiple_coulomb_scattering.h +31 -10
  33. xcoll/scattering_routines/everest/nuclear_interaction.h +86 -0
  34. xcoll/scattering_routines/geometry/__init__.py +6 -0
  35. xcoll/scattering_routines/geometry/collimator_geometry.h +219 -0
  36. xcoll/scattering_routines/geometry/crystal_geometry.h +150 -0
  37. xcoll/scattering_routines/geometry/geometry.py +26 -0
  38. xcoll/scattering_routines/geometry/get_s.h +92 -0
  39. xcoll/scattering_routines/geometry/methods.h +111 -0
  40. xcoll/scattering_routines/geometry/objects.h +154 -0
  41. xcoll/scattering_routines/geometry/rotation.h +23 -0
  42. xcoll/scattering_routines/geometry/segments.h +226 -0
  43. xcoll/scattering_routines/geometry/sort.h +184 -0
  44. {xcoll-0.3.5.dist-info → xcoll-0.4.0.dist-info}/METADATA +1 -1
  45. {xcoll-0.3.5.dist-info → xcoll-0.4.0.dist-info}/RECORD +48 -33
  46. xcoll/beam_elements/collimators_src/absorber.h +0 -141
  47. xcoll/collimator_settings.py +0 -457
  48. xcoll/impacts/__init__.py +0 -1
  49. xcoll/impacts/impacts.py +0 -102
  50. xcoll/impacts/impacts_src/impacts.h +0 -99
  51. xcoll/scattering_routines/everest/crystal.h +0 -1302
  52. xcoll/scattering_routines/everest/scatter.h +0 -169
  53. xcoll/scattering_routines/everest/scatter_crystal.h +0 -260
  54. {xcoll-0.3.5.dist-info → xcoll-0.4.0.dist-info}/LICENSE +0 -0
  55. {xcoll-0.3.5.dist-info → xcoll-0.4.0.dist-info}/NOTICE +0 -0
  56. {xcoll-0.3.5.dist-info → xcoll-0.4.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,98 @@
1
+ // copyright ############################### #
2
+ // This file is part of the Xcoll Package. #
3
+ // Copyright (c) CERN, 2024. #
4
+ // ######################################### #
5
+
6
+
7
+ #ifndef XCOLL_IMPACTS_H
8
+ #define XCOLL_IMPACTS_H
9
+
10
+ // TODO: do we need to pass RecordIndex?
11
+ // probably can do RecordIndex record_index = InteractionRecordData_getp__index(record); ?
12
+ /*gpufun*/
13
+ int64_t InteractionRecordData_log(InteractionRecordData record, RecordIndex record_index, LocalParticle* parent,
14
+ int64_t interaction){
15
+ // This can be used for a point-like interaction where there is no child (or because it's equal to the parent)
16
+ // or to log the parent first, to be followed up with InteractionRecordData_log_child on the same slot
17
+
18
+ int64_t i_slot = -1;
19
+ if (record){
20
+ // Get a slot in the record (this is thread safe)
21
+ i_slot = RecordIndex_get_slot(record_index);
22
+ // The returned slot id is negative if record is NULL or if record is full
23
+
24
+ if (i_slot>=0){
25
+ InteractionRecordData_set_at_element(record, i_slot, LocalParticle_get_at_element(parent));
26
+ InteractionRecordData_set_at_turn(record, i_slot, LocalParticle_get_at_turn(parent));
27
+ InteractionRecordData_set_ds(record, i_slot, 0);
28
+ InteractionRecordData_set__inter(record, i_slot, interaction);
29
+
30
+ double charge_ratio = LocalParticle_get_charge_ratio(parent);
31
+ double mass_ratio = charge_ratio / LocalParticle_get_chi(parent);
32
+ double energy = ( LocalParticle_get_ptau(parent) + 1 / LocalParticle_get_beta0(parent)
33
+ ) * mass_ratio * LocalParticle_get_p0c(parent);
34
+ // All fields have to be written, or the arrays will not have the same length
35
+ // TODO: maybe this is not true, as we are setting by slot index? Don't the arrays come pre-initialised?
36
+ InteractionRecordData_set_parent_id(record, i_slot, LocalParticle_get_particle_id(parent));
37
+ InteractionRecordData_set_parent_x(record, i_slot, LocalParticle_get_x(parent));
38
+ InteractionRecordData_set_parent_px(record, i_slot, LocalParticle_get_px(parent));
39
+ InteractionRecordData_set_parent_y(record, i_slot, LocalParticle_get_y(parent));
40
+ InteractionRecordData_set_parent_py(record, i_slot, LocalParticle_get_py(parent));
41
+ InteractionRecordData_set_parent_zeta(record, i_slot, LocalParticle_get_zeta(parent));
42
+ InteractionRecordData_set_parent_delta(record, i_slot, LocalParticle_get_delta(parent));
43
+ InteractionRecordData_set_parent_energy(record, i_slot, energy);
44
+ InteractionRecordData_set_parent_mass(record, i_slot, mass_ratio*LocalParticle_get_mass0(parent));
45
+ InteractionRecordData_set_parent_charge(record, i_slot, charge_ratio*LocalParticle_get_q0(parent));
46
+ // TODO: particle info
47
+ InteractionRecordData_set_parent_z(record, i_slot, -1);
48
+ InteractionRecordData_set_parent_a(record, i_slot, -1);
49
+ InteractionRecordData_set_parent_pdgid(record, i_slot, -1);
50
+
51
+ // TODO: maybe this is not needed
52
+ InteractionRecordData_set_child_id(record, i_slot, -1);
53
+ InteractionRecordData_set_child_x(record, i_slot, -1);
54
+ InteractionRecordData_set_child_px(record, i_slot, -1);
55
+ InteractionRecordData_set_child_y(record, i_slot, -1);
56
+ InteractionRecordData_set_child_py(record, i_slot, -1);
57
+ InteractionRecordData_set_child_zeta(record, i_slot, -1);
58
+ InteractionRecordData_set_child_delta(record, i_slot, -1);
59
+ InteractionRecordData_set_child_energy(record, i_slot, -1);
60
+ InteractionRecordData_set_child_mass(record, i_slot, -1);
61
+ InteractionRecordData_set_child_charge(record, i_slot, -1);
62
+ InteractionRecordData_set_child_z(record, i_slot, -1);
63
+ InteractionRecordData_set_child_a(record, i_slot, -1);
64
+ InteractionRecordData_set_child_pdgid(record, i_slot, -1);
65
+ }
66
+ }
67
+ // printf("Logging %i in slot %i\n", interaction, i_slot);
68
+ return i_slot;
69
+ }
70
+
71
+ /*gpufun*/
72
+ void InteractionRecordData_log_child(InteractionRecordData record, int64_t i_slot, LocalParticle* child, double ds){
73
+ if (record && i_slot>=0){
74
+ InteractionRecordData_set_ds(record, i_slot, ds);
75
+
76
+ double charge_ratio = LocalParticle_get_charge_ratio(child);
77
+ double mass_ratio = charge_ratio / LocalParticle_get_chi(child);
78
+ double energy = ( LocalParticle_get_ptau(child) + 1 / LocalParticle_get_beta0(child)
79
+ ) * mass_ratio * LocalParticle_get_p0c(child);
80
+ InteractionRecordData_set_child_id(record, i_slot, LocalParticle_get_particle_id(child));
81
+ InteractionRecordData_set_child_x(record, i_slot, LocalParticle_get_x(child));
82
+ InteractionRecordData_set_child_px(record, i_slot, LocalParticle_get_px(child));
83
+ InteractionRecordData_set_child_y(record, i_slot, LocalParticle_get_y(child));
84
+ InteractionRecordData_set_child_py(record, i_slot, LocalParticle_get_py(child));
85
+ InteractionRecordData_set_child_zeta(record, i_slot, LocalParticle_get_zeta(child));
86
+ InteractionRecordData_set_child_delta(record, i_slot, LocalParticle_get_delta(child));
87
+ InteractionRecordData_set_child_energy(record, i_slot, energy);
88
+ InteractionRecordData_set_child_mass(record, i_slot, mass_ratio*LocalParticle_get_mass0(child));
89
+ InteractionRecordData_set_child_charge(record, i_slot, charge_ratio*LocalParticle_get_q0(child));
90
+ // TODO: particle info
91
+ InteractionRecordData_set_child_z(record, i_slot, -1);
92
+ InteractionRecordData_set_child_a(record, i_slot, -1);
93
+ InteractionRecordData_set_child_pdgid(record, i_slot, -1);
94
+ // printf("Slot %i: length %f\n", i_slot, ds);
95
+ }
96
+ }
97
+
98
+ #endif /* XCOLL_IMPACTS_H */
@@ -1,6 +1,6 @@
1
1
  # copyright ############################### #
2
2
  # This file is part of the Xcoll Package. #
3
- # Copyright (c) CERN, 2023. #
3
+ # Copyright (c) CERN, 2024. #
4
4
  # ######################################### #
5
5
 
6
6
 
@@ -10,15 +10,16 @@ source = r'''
10
10
 
11
11
  #define XC_UNITIALISED 0 // NAN // Do not use
12
12
 
13
- #define XC_ENTER_JAW -1 // JI // point (no children) Set ds > 0 if entering later
14
- #define XC_EXIT_JAW -2 // JO // point (no children)
13
+ #define XC_ENTER_JAW_L -1 // JI // point (no children) Set ds > 0 if entering later
14
+ #define XC_ENTER_JAW_R -2 // JI // point (no children) Set ds > 0 if entering later
15
+ #define XC_EXIT_JAW -3 // JO // point (no children)
16
+ #define XC_ENTER_JAW -4 // JI // point (no children) Set ds > 0 if entering later still here for compatibility
15
17
  #define XC_ABSORBED 1 // A // point (no children) Don't use 0 (is default for unitialised)
16
18
  #define XC_MULTIPLE_COULOMB_SCATTERING 13 // MCS // continuous
17
19
  #define XC_PN_ELASTIC 14 // PN // point (no children)
18
20
  #define XC_PP_ELASTIC 15 // PP // point (no children)
19
21
  #define XC_SINGLE_DIFFRACTIVE 16 // SD // point (no children)
20
22
  #define XC_COULOMB 17 // C // point (no children)
21
- #define XC_RUTHERFORD 18 // RU // point (no children)
22
23
  #define XC_CHANNELING 100 // CH // continuous
23
24
  #define XC_DECHANNELING 101 // DCH // point (no children)
24
25
  #define XC_VOLUME_REFLECTION_TRANS_CH 102 // VRCH // point (no children) Transition region around +-xpcrit
@@ -43,3 +44,9 @@ shortcuts = {
43
44
  for line in source.split('\n')
44
45
  if len(line.split()) > 1 and line.split()[1][:3] == 'XC_' # select the source lines with the definitions
45
46
  }
47
+
48
+ is_point = {
49
+ int(line.split()[2]): line.split()[6].lower() == 'point'
50
+ for line in source.split('\n')
51
+ if len(line.split()) > 1 and line.split()[1][:3] == 'XC_' # select the source lines with the definitions
52
+ }
xcoll/line_tools.py ADDED
@@ -0,0 +1,83 @@
1
+ # copyright ############################### #
2
+ # This file is part of the Xcoll Package. #
3
+ # Copyright (c) CERN, 2024. #
4
+ # ######################################### #
5
+
6
+ import numpy as np
7
+ import xtrack as xt
8
+
9
+ from .beam_elements import element_classes, collimator_classes
10
+
11
+
12
+ def assign_optics_to_collimators(line, nemitt_x=None, nemitt_y=None, twiss=None):
13
+ if not line._has_valid_tracker():
14
+ raise Exception("Please build tracker before setting the openings!")
15
+ names = line.get_elements_of_type(collimator_classes)[1]
16
+ tw_upstream, tw_downstream = get_optics_at(names, twiss=twiss, line=line)
17
+ beta_gamma_rel = line.particle_ref._xobject.gamma0[0]*line.particle_ref._xobject.beta0[0]
18
+ for coll in names:
19
+ print(coll)
20
+ line[coll].assign_optics(name=coll, nemitt_x=nemitt_x, nemitt_y=nemitt_x, twiss_upstream=tw_upstream,
21
+ twiss_downstream=tw_downstream, beta_gamma_rel=beta_gamma_rel)
22
+
23
+ def get_optics_at(names, *, twiss=None, line=None):
24
+ if twiss is None:
25
+ if not line._has_valid_tracker():
26
+ raise Exception("Please pass a line and build tracker before computing the optics for the openings!")
27
+ twiss = line.twiss()
28
+ if not hasattr(names, '__iter__') and not isinstance(names, str):
29
+ names = [names]
30
+ coll_entry_mask = twiss.mask[names]
31
+ tw_entry = twiss.rows[coll_entry_mask]
32
+ tw_exit = twiss.rows[coll_entry_mask+1]
33
+ tw_exit.name = tw_entry.name
34
+ return tw_entry, tw_exit
35
+
36
+
37
+ def open_collimators(line, names=None):
38
+ if names is None:
39
+ names = line.get_elements_of_type(collimator_classes)[1]
40
+ if len(names) == 0:
41
+ print("No collimators found in line.")
42
+ else:
43
+ for coll in names:
44
+ line[coll].open_jaws(keep_tilts=False)
45
+ line[coll].gap = None
46
+
47
+ def send_to_parking(line, names=None):
48
+ if names is None:
49
+ names = line.get_elements_of_type(collimator_classes)[1]
50
+ if len(names) == 0:
51
+ print("No collimators found in line.")
52
+ else:
53
+ raise NotImplementedError("Need to move this to new type manager or so.")
54
+
55
+
56
+ def enable_scattering(line):
57
+ elements = line.get_elements_of_type(element_classes)[0]
58
+ if len(elements) == 0:
59
+ print("No xcoll elements found in line.")
60
+ else:
61
+ nemitt_x = None
62
+ nemitt_y = None
63
+ for el in elements:
64
+ if hasattr(el, 'optics') and el.optics is not None:
65
+ if nemitt_x is None:
66
+ nemitt_x = el.nemitt_x
67
+ if nemitt_y is None:
68
+ nemitt_y = el.nemitt_y
69
+ if not np.isclose(el.nemitt_x, nemitt_x) \
70
+ or not np.isclose(el.nemitt_x, nemitt_x):
71
+ raise ValueError("Not all collimators have the same "
72
+ + "emittance. This is not supported.")
73
+ el.enable_scattering()
74
+ # self.line.tracker.io_buffer = self._io_buffer
75
+ # self._set_record_interaction_record()
76
+
77
+ def disable_scattering(line):
78
+ elements = line.get_elements_of_type(element_classes)[0]
79
+ if len(elements) == 0:
80
+ print("No xcoll elements found in line.")
81
+ else:
82
+ for el in elements:
83
+ el.disable_scattering()
xcoll/lossmap.py ADDED
@@ -0,0 +1,209 @@
1
+ # copyright ############################### #
2
+ # This file is part of the Xcoll Package. #
3
+ # Copyright (c) CERN, 2024. #
4
+ # ######################################### #
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ from pathlib import Path
9
+ import json
10
+
11
+ import xtrack as xt
12
+ import xpart as xp
13
+ import xobjects as xo
14
+
15
+ from .beam_elements import collimator_classes
16
+
17
+
18
+ class LossMap:
19
+
20
+ def __init__(self, line, part, *, line_is_reversed, interpolation=0.1,
21
+ weights=None, weight_function=None):
22
+ self._line = line
23
+ self._line_is_reversed = line_is_reversed
24
+ self._machine_length = line.get_length()
25
+ self._part = part
26
+ self._interpolation = interpolation
27
+ if weights is None:
28
+ if weight_function is None:
29
+ self._weights = np.ones(len(part.x))
30
+ else:
31
+ self._weights = _create_weights_from_initial_state(part, weight_function)
32
+ else:
33
+ if weight_function is not None:
34
+ raise ValueError("Use either 'weights' or 'weight_function', not both!")
35
+ self._weights = part.sort(interleave_lost_particles=True)
36
+
37
+ # loss location refinement
38
+ if interpolation is not None:
39
+ self._interpolate()
40
+
41
+ self._make_coll_summary()
42
+ coll_summary = self._summary[self._summary.nabs > 0].to_dict('list')
43
+ aper_s, aper_names, aper_nabs = self._get_aperture_losses()
44
+
45
+ self._lossmap = {
46
+ 'collimator': {
47
+ 's': coll_summary['s'],
48
+ 'name': coll_summary['collname'],
49
+ 'length': coll_summary['length'],
50
+ 'n': coll_summary['nabs']
51
+ }
52
+ ,
53
+ 'aperture': {
54
+ 's': aper_s,
55
+ 'name': aper_names,
56
+ 'n': aper_nabs
57
+ }
58
+ ,
59
+ 'machine_length': self._machine_length
60
+ ,
61
+ 'interpolation': interpolation
62
+ ,
63
+ 'reversed': self._line_is_reversed
64
+ }
65
+
66
+
67
+ def to_json(self, file):
68
+ with open(Path(file), 'w') as fid:
69
+ json.dump(self._lossmap, fid, indent=True, cls=xo.JEncoder)
70
+
71
+ def save_summary(self, file):
72
+ with open(Path(file), 'w') as fid:
73
+ fid.write(self._summary.__repr__())
74
+
75
+
76
+ @property
77
+ def lossmap(self):
78
+ return self._lossmap
79
+
80
+ @property
81
+ def summary(self):
82
+ return self._summary
83
+
84
+ @property
85
+ def line(self):
86
+ return self._line
87
+
88
+ @property
89
+ def line_is_reversed(self):
90
+ return self._line_is_reversed
91
+
92
+ @property
93
+ def machine_length(self):
94
+ return self._machine_length
95
+
96
+ @property
97
+ def part(self):
98
+ return self._part
99
+
100
+ @property
101
+ def interpolation(self):
102
+ return self._interpolation
103
+
104
+ @property
105
+ def weights(self):
106
+ return self._weights
107
+
108
+
109
+ def _interpolate(self):
110
+ new_state = self._part.state.copy()
111
+ new_elem = self._part.at_element.copy()
112
+
113
+ # Correct particles that are at an aperture directly after a collimator
114
+ for idx, elem in enumerate(self._part.at_element):
115
+ if (self._part.state[idx] == 0 and elem > 0
116
+ and self._line.element_names[elem-1] in
117
+ self._line.get_elements_of_type(collimator_classes)[1]):
118
+ print(f"Found at {self._line.element_names[elem]}, "
119
+ + f"should be {self._line.element_names[elem-1]}")
120
+ new_elem[idx] = elem - 1
121
+ what_type = self._line[elem-1].__class__.__name__
122
+ if what_type == 'EverestCollimator':
123
+ new_state[idx] = -331
124
+ elif what_type == 'EverestCrystal':
125
+ new_state[idx] = -332
126
+ elif what_type == 'FlukaCollimator':
127
+ new_state[idx] = -334 # TODO: what if crystal?
128
+ elif what_type == 'Geant4Collimator':
129
+ new_state[idx] = -337 # TODO: what if crystal?
130
+ elif what_type == 'BlackAbsorber':
131
+ new_state[idx] = -340
132
+ else:
133
+ raise ValueError(f"Unknown collimator type {what_type}")
134
+ self._part.state = new_state
135
+ self._part.at_element = new_elem
136
+
137
+ # do the interpolation
138
+ aper_s = list(self._part.s[self._part.state==0])
139
+ if len(aper_s) > 0:
140
+ print("Performing the aperture losses refinement.")
141
+ loss_loc_refinement = xt.LossLocationRefinement(
142
+ self._line,
143
+ n_theta = 360, # Angular resolution
144
+ r_max = 0.5, # Maximum transverse aperture [m]
145
+ dr = 50e-6, # Transverse accuracy [m]
146
+ ds = self.interpolation # Longitudinal accuracy [m]
147
+ )
148
+ loss_loc_refinement.refine_loss_location(self._part)
149
+
150
+
151
+ def _make_coll_summary(self):
152
+ collimator_names = self._line.get_elements_of_type(collimator_classes)[1]
153
+ coll_mask = (self._part.state <= -330) & (self._part.state >= -340)
154
+ coll_losses = np.array([self._line.element_names[i]
155
+ for i in self._part.at_element[coll_mask]])
156
+ coll_lengths = [self._line[j].length for j in collimator_names]
157
+ coll_pos = [(self._line.get_s_position(i) + self._line[i].length/2)
158
+ for i in collimator_names]
159
+
160
+ if self._line is reversed:
161
+ coll_pos = [self._machine_length - s for s in coll_pos]
162
+
163
+ coll_types = [self._line[i].__class__.__name__ for i in collimator_names]
164
+ coll_weights = self._weights[coll_mask]
165
+ nabs = [coll_weights[coll_losses == j].sum() for j in collimator_names]
166
+
167
+ self._summary = pd.DataFrame({
168
+ 'collname': collimator_names,
169
+ 'nabs': nabs, # of particles lost on collimators
170
+ 'length': coll_lengths,
171
+ 's': coll_pos,
172
+ 'type': coll_types
173
+ })
174
+
175
+
176
+ def _get_aperture_losses(self):
177
+ # Get s position per particle (lost on aperture)
178
+ aper_mask = self._part.state == 0
179
+ aper_s = list(self._part.s[aper_mask])
180
+
181
+ if len(aper_s) == 0:
182
+ return [], [], []
183
+ if self._line_is_reversed:
184
+ aper_s = [ self._machine_length - s for s in aper_s ]
185
+
186
+ # Store names of aperture markers
187
+ aper_names = [self._line.element_names[i] for i in self._part.at_element[aper_mask]]
188
+ name_dict = dict(zip(aper_s, aper_names)) # TODO: not floating-point-safe and slow
189
+
190
+ # Create output arrays
191
+ aper_pos = np.unique(aper_s)
192
+ aper_weights = self._weights[aper_mask]
193
+ aper_nabs = [aper_weights[aper_s == j].sum() for j in aper_pos]
194
+ aper_names = [name_dict[ss] for ss in aper_pos]
195
+
196
+ return aper_pos, aper_names, aper_nabs
197
+
198
+
199
+ def _create_weights_from_initial_state(part, function):
200
+ if len(function) == 4:
201
+ return function[0](part.x)*function[1](part.px)*\
202
+ function[2](part.y)*function[3](part.py)
203
+ elif len(function) == 6:
204
+ return function[0](part.x)*function[1](part.px)*\
205
+ function[2](part.y)*function[3](part.py)*\
206
+ function[4](part.zeta)*function[5](part.delta)
207
+ else:
208
+ raise NotImplementedError
209
+