diff --git a/mccode_antlr/instr/instr.py b/mccode_antlr/instr/instr.py index 698c02a..77bf139 100644 --- a/mccode_antlr/instr/instr.py +++ b/mccode_antlr/instr/instr.py @@ -221,56 +221,6 @@ def _getpath(self, filename: str): return registry.path(filename).absolute().resolve() return Path() - # def _replace_env_getpath_cmd(self, flags: Union[str, bytes]): - # """Replace CMD, ENV, and GETPATH directives from a flag string or byte-array""" - # # Mimics McCode-3/tools/Python/mccodelib/cflags.py:evaluate_dependency_str - # # - # is_bytes = isinstance(flags, bytes) - # to_str = (lambda b: b.decode()) if is_bytes else (lambda b: b) - # from_str = (lambda b: b.encode()) if is_bytes else (lambda b: b) - # - # def getpath(chars): - # return from_str(str(self._getpath(to_str(chars)).as_posix())) - # - # def eval_cmd(chars): - # from subprocess import run, CalledProcessError - # try: - # proc = run(to_str(chars), check=True, shell=True, capture_output=True) - # output = proc.stdout - # except CalledProcessError as error: - # raise RuntimeError(f"Calling {to_str(chars)} resulted in error {error}") - # output = [line.strip() for line in to_str(output).splitlines() if line.strip()] - # if len(output) > 1: - # raise RuntimeError(f"Calling {to_str(chars)} produced more than one line of output") - # return from_str(output[0] if output else '') - # - # def eval_env(chars): - # from os import environ - # return from_str(environ.get(to_str(chars), '')) - # - # def replace(chars, start, replacer): - # if start not in chars: - # return chars - # before, after = chars.split(start, 1) - # if from_str('(') != after[0]: - # raise ValueError(f'Missing opening parenthesis in dependency string after {to_str(start)}') - # if from_str(')') not in after: - # raise ValueError(f'Missing closing parenthesis in dependency string after {to_str(start)}') - # dep, after = after[1:].split(from_str(')'), 1) - # if start in dep: - # raise ValueError(f'Nested {to_str(start)} in dependency string') - # print(f'{type(before)} -- {before}') - # print(f'{type(replacer(dep))}') - # return before + replacer(dep) + replace(after, start, replacer) - # - # keys = [b'ENV', b'GETPATH', b'CMD'] if is_bytes else ['ENV', 'GETPATH', 'CMD'] - # - # print(f'The input {flags} is a {type(flags)} object, so {is_bytes = }') - # for key, worker in zip(keys, [eval_env, getpath, eval_cmd]): - # flags = replace(flags, key, worker) - # - # return flags - def _replace_env_getpath_cmd(self, flags: str): """Replace CMD, ENV, and GETPATH directives from a flag string""" @@ -435,9 +385,11 @@ def mcpl_split(self, after, filename=None, output_parameters=None, input_paramet input_parameters = (filename_parameter,) + input_parameters if not any(p.name == 'verbose' for p in input_parameters): input_parameters = (ComponentParameter('verbose', Expr.float(0)),) + input_parameters - # the MCPL input component _is_ the origin of its simulation - second.make_instance(fc.name, 'MCPL_input', (Vector(), None), (Angles(), None), - parameters=input_parameters) + # the MCPL input component _is_ the origin of its simulation, but must be placed relative to other components. + # so we need the *absolute* position and orientation of the removed component: + abs_at_rel = fc.orientation.position(), None + abs_rot_rel = fc.orientation.angles(), None + second.make_instance(fc.name, 'MCPL_input', abs_at_rel, abs_rot_rel, parameters=input_parameters) # move the newly added component to the front of the list: second.components = (second.components[-1],) + second.components[:-1] diff --git a/test/test_instr.py b/test/test_instr.py index 5a6e874..15b44c6 100644 --- a/test/test_instr.py +++ b/test/test_instr.py @@ -1,6 +1,10 @@ from unittest import TestCase from zenlog import log +from mccode_antlr.instr import Instr, Instance +from mccode_antlr.common.expression import Expr +from mccode_antlr.instr.orientation import Vector + def parse_instr_string(instr_source: str): from mccode_antlr.loader import parse_mcstas_instr @@ -368,29 +372,44 @@ def test_copy(self): self.assertEqual(len(instr.parameters), len(instr_copy.parameters) - 1) def test_mcpl_split(self): - from mccode_antlr.instr import Instr instr_source = """ - DEFINE INSTRUMENT test_copy(par0=3.14159, double par1 = 49, int par2 = 1010110 - , string par3="this is a long string with spaces", - - string par4, int par5, double par6, par7) + DEFINE INSTRUMENT test_copy() TRACE - COMPONENT first = Arm() AT (0, 0, 0) ABSOLUTE - COMPONENT second = Arm() AT (0, 0, 1) RELATIVE first ROTATED (0, 90, 0) RELATIVE first + COMPONENT origin = Arm() AT (0, 0, 0) ABSOLUTE + COMPONENT first = Arm() AT (0, 0, 100) RELATIVE origin ROTATED (0, 90, 0) RELATIVE origin + COMPONENT second = Arm() AT (0, 0, 1) RELATIVE first ROTATED (-90, 0, 0) RELATIVE first + COMPONENT split_here = Arm() AT (0, 0, 0) RELATIVE second + COMPONENT third = Arm() AT (0, 0, 10) RELATIVE second END """ instr = parse_instr_string(instr_source) - before, after = instr.mcpl_split('first', filename='test_mcpl_split') + before, after = instr.mcpl_split('split_here', filename='test_mcpl_split') self.assertTrue(isinstance(before, Instr)) self.assertTrue(isinstance(after, Instr)) - self.assertEqual(len(before.components), 1) + self.assertEqual(len(before.components), 4) self.assertEqual(len(after.components), 2) - self.assertEqual(before.components[0].name, 'first') - self.assertEqual(after.components[0].name, 'first') - self.assertEqual(after.components[1].name, 'second') - self.assertEqual(before.components[0].type.name, 'MCPL_output') + for inst, name in zip(before.components, ('origin', 'first', 'second', 'split_here')): + self.assertEqual(name, inst.name) + + for inst, name in zip(after.components, ('split_here', 'third')): + self.assertEqual(name, inst.name) + + self.assertEqual(before.components[-1].type.name, 'MCPL_output') self.assertEqual(after.components[0].type.name, 'MCPL_input') + # It is imperative that the split-point did not move: + sp = after.components[0] + at_rel = sp.at_relative + self.assertTrue(at_rel[1] is None) # the position is absolute + pos = Vector(Expr.float(1), Expr.float(0), Expr.float(100)) + self.assertEqual(pos, at_rel[0]) + + # The final component had a relative position which spanned the split point, so it should now be absolute + self.assertTrue(after.components[-1].at_relative[1] is None) + # rounding makes third.x 1.000...07 + for a, b in zip(Vector(Expr.float(1), Expr.float(10), Expr.float(100)), after.components[-1].at_relative[0]): + self.assertAlmostEqual(a, b) + def test_tas1_c1(self): from mccode_antlr.loader.loader import parse_mccode_instr_parameters contents = """ DEFINE INSTRUMENT tas(PHM=-37.077,TTM=-74,C1=30) TRACE END""" @@ -437,9 +456,6 @@ def test_used_parameter_check(self): def test_split_broken_reference(self): from textwrap import dedent - from mccode_antlr.instr import Instance - from mccode_antlr.common.expression import Expr - from mccode_antlr.instr.orientation import Vector instr = dedent("""\ DEFINE INSTRUMENT test_tof(phase/"degree"=0) TRACE @@ -451,14 +467,26 @@ def test_split_broken_reference(self): COMPONENT guide = Guide_gravity(w1=0.01, w2=0.05, h1=0.01, h2=0.05, l=8.0, m=3.5, G=-9.82) AT (0, 0, 1) ABSOLUTE COMPONENT guide_end = Arm() AT (0, 0, 8.0) RELATIVE guide COMPONENT chopper = DiskChopper(radius=0.35, nu=14, phase=phase, theta_0=115) AT (0, 0, 0.01) RELATIVE guide_end - COMPONENT split_at = Arm() AT (0, 0, 1e-08) RELATIVE chopper + COMPONENT split_before = Arm() AT (0, 0, 1e-08) RELATIVE chopper + COMPONENT split_after = Arm() AT (0, 0, 0) RELATIVE split_before COMPONENT sample = Incoherent( radius=0.005, yheight=0.02, thickness=0.001, focus_ah=2.0, focus_aw=2.0, target_x=1.0, target_y=0.0, target_z=0.0, Etrans=0.0, deltaE=0.2 ) AT (0, 0, 1) RELATIVE chopper END""") instr = parse_instr_string(instr) - first, second = instr.split('split_at', remove_unused_parameters=True) + first, second = instr.split('split_before', remove_unused_parameters=True) + # The last component in the first part of the instrument _is_ split_before + self.assertEqual('split_before', first.components[-1].name) + # The position of the split_at component should be absolute and the same as in the main instrument + split_after = second.components[0] + self.assertEqual(split_after.name, 'split_after') + at_ref = split_after.at_relative + self.assertTrue(isinstance(at_ref[0], Vector)) + self.assertTrue(at_ref[1] is None) + v = Vector(Expr.float(0), Expr.float(0), Expr.float(1 + 8 + 0.01 + 1E-8)) + self.assertEqual(v, at_ref[0]) + # The sample _should not_ still depend on the chopper, which is only present in the first instrument! sample = second.components[-1] self.assertEqual(sample.name, 'sample')