Changeset 3274 for branches

Show
Ignore:
Timestamp:
01/26/12 21:06:59 (4 months ago)
Author:
erkn
Message:

Linear scanning based on ranges and lists

Location:
branches/mcxtrace-1.0/src/mcrun2
Files:
1 added
2 modified

Legend:

Unmodified
Added
Removed
  • branches/mcxtrace-1.0/src/mcrun2/main.py

    r3273 r3274  
    55from os.path import isfile, isdir, abspath, dirname 
    66from optparse import OptionParser, OptionGroup, OptionValueError 
     7from decimal import Decimal, InvalidOperation 
    78 
    89from mcstas import McStas 
     10from optimisation import Scanner, LinearInterval 
    911 
    1012LOG = logging.getLogger('mcstas') 
     
    4345        help='set number of scan points') 
    4446 
     47    add('-L', '--list', 
     48        action='store_true', 
     49        help='use a fixed list of points for linear scanning') 
     50 
    4551    # Multiprocessing 
    4652    add('--mpi', 
     
    7177 
    7278    add('--optimise-file', 
    73         metavar='FILE', 
     79        metavar='FILE', default='mcstas.dat', 
    7480        help='store optimisation results in FILE ' 
    75              '(defaults to: "mcoptim_####.dat")') 
     81             '(defaults to: "mcstas.dat")') 
    7682 
    7783    # Misc options 
     
    96102    opt = OptionGroup(parser, 'Instrument options') 
    97103    add = opt.add_option 
    98  
    99104 
    100105    # Misc options 
     
    120125    # Data options 
    121126    dir_exists = lambda path: isdir(abspath(path)) 
     127 
    122128    def check_file(exist=True): 
    123129        ''' Validate the path to a file ''' 
     
    126132        else: 
    127133            def is_valid(path): 
    128                 ''' Ensure that path to file exists and filename is provided ''' 
     134                ''' Ensure path to file exists and filename is provided ''' 
    129135                if not dir_exists(dirname(path)): 
    130136                    return False 
     
    174180 
    175181 
     182def is_decimal(string): 
     183    ''' Check if string is parsable as decimal/float ''' 
     184    try: 
     185        Decimal(string) 
     186        return True 
     187    except InvalidOperation: 
     188        return False 
     189 
     190 
     191def get_parameters(options): 
     192    ''' Get fixed and scan/optimise parameters ''' 
     193    fixed_params = {} 
     194    intervals = {} 
     195    for param in options.params: 
     196        if '=' in param: 
     197            key, value = param.split('=', 1) 
     198            interval = value.split(',') 
     199            # When just one point is present, fix as constant 
     200            if len(interval) == 1: 
     201                fixed_params[key] = Decimal(value) 
     202            else: 
     203                LOG.debug('interval: %s', interval) 
     204                intervals[key] = interval 
     205        else: 
     206            LOG.warning('Ignoring invalid parameter: "%s"', param) 
     207    return (fixed_params, intervals) 
     208 
     209 
    176210def main(): 
    177211    ''' Main routine ''' 
    178212 
    179213    # Setup logging 
    180     formatter = logging.Formatter('%(created)s, %(levelname)8s: %(message)s') 
     214    formatter = logging.Formatter('%(created).2f, %(levelname)8s: %(message)s') 
    181215 
    182216    handler = logging.StreamHandler() 
     
    211245    # Run McStas 
    212246    mcstas = McStas(options.instr) 
    213  
    214     # Set parameters 
    215     for param in options.params: 
    216         if '=' in param: 
    217             key, value = param.split('=', 1) 
    218             mcstas.setParameter(key, value) 
    219         else: 
    220             LOG.warning('Ignoring invalid parameter: "%s"', param) 
    221  
    222247    mcstas.prepare(options) 
    223     mcstas.run() 
     248    instrs = mcstas.get_info().get_instrument() 
     249    LOG.debug('info: %s', instrs) 
     250 
     251    (fixed_params, intervals) = get_parameters(options) 
     252 
     253    # Set fixed parameters 
     254    for key, value in fixed_params.items(): 
     255        mcstas.set_parameter(key, value) 
     256 
     257    # Check for linear scanning 
     258    linear_interval = None 
     259 
     260    # Can't both do list and interval scanning 
     261    if options.list and options.numpoints: 
     262        raise OptionValueError('--numpoints cannot be used with --list') 
     263 
     264    elif options.list: 
     265        if len(intervals) == 0: 
     266            raise OptionValueError( 
     267                '--list was chosen but no lists was presented.') 
     268        points = len(intervals.values()[0]) 
     269        if not(all(map(lambda i: len(i) == points, intervals.values()))): 
     270            raise OptionValueError( 
     271                'All variables much have an equal amount of points.') 
     272        linear_interval = LinearInterval.from_list( 
     273            points, intervals) 
     274 
     275    elif options.numpoints is not None: 
     276        if options.numpoints < 2: 
     277            raise OptionValueError( 
     278                ('Cannot scan variable(s) %s using only one data point. ' 
     279                 'Please use -N to specify the number of points.') % \ 
     280                ', '.join(intervals.keys())) 
     281        # Check that input is valid decimals 
     282        if not all(map(lambda i: 
     283                       len(i) == 2 and 
     284                       all(map(is_decimal, i)), intervals.values())): 
     285            raise OptionValueError('Could not parse intervals.') 
     286 
     287        linear_interval = LinearInterval.from_range( 
     288            options.numpoints, intervals) 
     289 
     290    # Parameters for linear scanning present 
     291    if linear_interval: 
     292        scanner = Scanner(mcstas, intervals) 
     293        scanner.set_points(linear_interval) 
     294        scanner.run() 
     295    else: 
     296        mcstas.run() 
    224297 
    225298 
  • branches/mcxtrace-1.0/src/mcrun2/mcstas.py

    r3273 r3274  
    11 
     2import atexit 
     3import logging 
     4import os 
     5import re 
    26import tempfile 
    3 import atexit 
    4 import os 
    5 import logging 
     7import yaml 
    68 
    79from os.path import isfile, dirname, basename, splitext 
    810from subprocess import Popen, PIPE 
     11from decimal import Decimal 
    912 
    1013 
     
    3134                                                    ' '.join(self.args)) 
    3235 
     36 
    3337class Process: 
    3438    ''' An external process ''' 
     
    4852 
    4953        # Run executable 
    50         LOG.debug('CMD: %s %s', self.executable, args) 
     54        LOG.debug('CMD: %s %s', self.executable, ' '.join(args)) 
    5155        fid = Popen([self.executable] + args, 
    5256                    stdout=pipe, 
     
    8185        atexit.register(self.cleanup) 
    8286 
    83  
    84     def setParameter(self, key, value): 
     87    def set_parameter(self, key, value): 
    8588        ''' Set the value of an experiment parameter ''' 
    8689        self.params[key] = value 
    87  
    8890 
    8991    def prepare(self, options): 
     
    114116        # Compiler optimisation 
    115117        args = ['-o', self.binpath] + cflags + [self.cpath] 
    116  
    117118        Process(options.cc).run(args) 
    118  
    119119 
    120120    def run(self, pipe=False): 
     
    140140 
    141141        # Add parameters last 
    142         args += [ '%s=%s' % (key, value) for key, value in self.params.items() ] 
     142        args += ['%s=%s' % (key, value) 
     143                 for key, value in self.params.items()] 
    143144 
    144145        # Run McStas 
    145146        if not mpi: 
    146147            LOG.info('Running: %s', self.binpath) 
    147             Process(self.binpath).run(args, pipe=pipe) 
     148            return Process(self.binpath).run(args, pipe=pipe) 
    148149        else: 
    149150            LOG.info('Running via MPI: %s', self.binpath) 
    150151            mpi_args = ['-np', str(options.mpi), self.binpath] 
    151152            mpi_args += args 
    152             Process(options.mpirun).run(mpi_args, pipe=pipe) 
    153  
     153            return Process(options.mpirun).run(mpi_args, pipe=pipe) 
     154 
     155    def get_info(self): 
     156        return McStasInfo(Process(self.binpath).run(['--info'], pipe=True)) 
    154157 
    155158    def cleanup(self): 
     
    163166        os.rmdir(self.dir) 
    164167 
     168 
     169class Detector(object): 
     170    ''' A detector ''' 
     171    def __init__(self, name, intensity, error, count, path): 
     172        self.name = name 
     173        self.intensity = Decimal(intensity) 
     174        self.error = Decimal(error) 
     175        self.count = Decimal(count) 
     176        self.path = path 
     177 
     178 
     179class McStasInfo: 
     180    ''' Parsing McStas experiment information (--info) ''' 
     181 
     182    PARAMETERS_RE = re.compile(r'^\s*Parameters:(.*)', flags=re.MULTILINE) 
     183    SEPERATOR_RE = re.compile(r'^([^:]+):\s*') 
     184    QUOTE_RE = re.compile(r'^(\s*[^:]+):\s*([^\[\s].*)$', flags=re.MULTILINE) 
     185    GROUP_RE = re.compile(r'begin ([^\s]+)(.+)end \1', flags=re.DOTALL) 
     186    PARAM_RE = re.compile(r'^\s*Param:\s+"', flags=re.MULTILINE) 
     187 
     188    def __init__(self, data): 
     189        self.data = data 
     190        self.info = self._parse_info() 
     191 
     192    def _parse_info(self): 
     193        """ 
     194        Parse the raw McStas info output 
     195        The output resembles YAML but not quite. 
     196        It's converted to YAML by: 
     197          0. Ensuring a space after 'key:' -> 'key: ' 
     198          1. Adding qoutes 'key: value' -> 'key: "value"' 
     199          2. Changing 'begin foobar\n ...\n end foobar' -> 'foobar:\n' 
     200          3. Add unique suffix number to each param: 
     201               Param: lambda=0.7 
     202               Param: DM=1.8 
     203               --> 
     204               Param0: lambda=0.7 
     205               Param1: DM=1.8 
     206          4. Split up 'Parameters' to form a list 
     207        """ 
     208 
     209        def escape(line): 
     210            ''' Escape \ and " ''' 
     211            return line.replace('\\', '\\\\').replace('"', r'\"') 
     212 
     213        def quote(match): 
     214            ''' Quote a value ''' 
     215            return '%s: "%s"' % (match.group(1), escape(match.group(2))) 
     216 
     217        def param_number(match): 
     218            ''' Assign unique number to each param ''' 
     219            param_number.prev_param_number += 1 
     220            return match.group(0).replace('Param', 
     221                                          'Param%i' % param_number.prev_param_number) 
     222        # start count at 0 (previous is -1) 
     223        setattr(param_number, 'prev_param_number', -1) 
     224 
     225        def parameters_to_list(match): 
     226            old_str = match.group(1) 
     227            if old_str.strip(): 
     228                new_str = ' [%s]' % ','.join(match.group(1).split()) 
     229                return match.group(0).replace(old_str, new_str) 
     230            return match.group(0).strip() + ' []' 
     231 
     232        yaml_str = self.data 
     233        yaml_str = self.PARAMETERS_RE.sub(parameters_to_list, yaml_str) 
     234        yaml_str = self.SEPERATOR_RE.sub(r'\1: ', yaml_str) 
     235        yaml_str = self.QUOTE_RE.sub(quote, yaml_str) 
     236        yaml_str = self.GROUP_RE.sub(r'\1:\2', yaml_str) 
     237        yaml_str = self.PARAM_RE.sub(param_number, yaml_str) 
     238 
     239        return yaml.load(yaml_str) 
     240 
     241    def get(self, key): 
     242        return self.info[key] 
     243 
     244    def get_simulation(self): 
     245        return self.get('simulation') 
     246 
     247    def get_instrument(self): 
     248        return self.get('instrument') 
     249 
     250 
     251class McStasResult: 
     252    ''' Parsing of McStas experiment output ''' 
     253 
     254    DETECTOR_RE = r'Detector: ([^\s]+)_I=([^ ]+) \1_ERR=([^\s]+) \1_N=([^ ]+) "(\1[^"]+)"' 
     255 
     256    def __init__(self, data): 
     257        self.data = data 
     258        self.detectors = None 
     259 
     260    def get_detectors(self): 
     261        ''' Extract detector information ''' 
     262        if self.detectors is not None: 
     263            return self.detectors 
     264        res = re.findall(self.DETECTOR_RE, self.data) 
     265 
     266        return [Detector(name, intensity, error, count, path) 
     267                for name, intensity, error, count, path in res]