Commit c99afe2b authored by Giovanni La Mura's avatar Giovanni La Mura
Browse files

Create a script to scale models by multiplicative factors

parent afa55aab
Loading
Loading
Loading
Loading
+276 −0
Original line number Original line Diff line number Diff line
#!/usr/bin/env python3

#   Copyright (C) 2025   INAF - Osservatorio Astronomico di Cagliari
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#   
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#   
#   A copy of the GNU General Public License is distributed along with
#   this program in the COPYING file. If not, see: <https://www.gnu.org/licenses/>.

## @package scale_model
#  \brief Script to re-scale an existing model by multiplicative factor.
#
#  This script is designed to create scaled versions of pre-existing
#  models, to allow for quick production of sets of particle models
#  characterized by the same structure, but with a different size.
#
#  The script execution requires python3.

# import pdb # uncomment this line for debugging
import re
from sys import argv

## \cond
int_reg = re.compile(r'-?[0-9]+')
float_reg = re.compile(r'-?[0-9]+\.[0-9]+([eEdD][-+]?[0-9]+)?')
## \endcond

## \brief Main execution code
#
# `main()` is the function that handles the creation of the script configuration
# and the execution of the comparison. It returns an integer value corresponding
# to the script exit code.
#
# \returns errors: `int` Number of runtime errors (0 means successful run).
def main():
    config = {}
    errors = 0
    try:
        config = parse_arguments()
    except ValueError as ex:
        print(ex)
        print("\nType \"scale_model.py --help\" to get more detailed help.")
        errors = 1
    if config['help_mode']:
        config['help_mode'] = True
        print_help()
    else:
        if (config['input_name'] == ''):
            print("ERROR: no input file name given (missing \"--in FILE\" option).")
            errors += 1
        if (config['mode'] == ''):
            print("ERROR: no mode specifier given (missing \"--mode=MODE\" option).")
            errors += 1
        if (errors == 0):
            if (config['mode'] == 'edfb'):
                try:
                    errors += scale_legacy_edfb(config)
                except Exception as ex:
                    print(ex)
                    errors += 1
            elif (config['mode'] == 'geom'):
                #try:
                errors += scale_legacy_geom(config)
                #except Exception as ex:
                #    print(ex)
                #    errors += 1
            else:
                print("ERROR: unknown mode \"%s\" (options are edfb|geom)"%(config['mode']))
                errors += 1
    return errors

## \brief Parse the command line arguments.
#
#  The script behaviour can be modified through a set of mandatory and optional
#  arguments. Mandatory arguments are those required to execute a meaningful
#  parsing and they are limited to the names of the files that need to be read
#  and written. The other arguments affect the format and the data fields that
#  are sought for.
#
#  \returns config: `dict` A dictionary containing the script configuration.
def parse_arguments():
    config = {
        'input_name': '',
        'output_name': '',
        'mode': '',
        'scale': 1.0,
        'help_mode': False
    }
    arg_index = 1
    skip_arg = False
    for arg in argv[1:]:
        if skip_arg:
            skip_arg = False
            continue
        split_arg = arg.split('=')
        if (arg.startswith("--in")):
            config['input_name'] = argv[arg_index + 1]
            arg_index += 1
            skip_arg = True
        elif (arg.startswith("--out")):
            config['output_name'] = argv[arg_index + 1]
            arg_index += 1
            skip_arg = True
        elif (arg.startswith("--mode=")):
            config['mode'] = split_arg[1]
        elif (arg.startswith("--scale=")):
            config['scale'] = float(split_arg[1])
        elif (arg.startswith("--help")):
            config['help_mode'] = True
        else:
            raise ValueError("Unrecognized argument \'{0:s}\'".format(arg))
        arg_index += 1
    if (config['output_name'] == ''):
        config['output_name'] = config['input_name'] + '_scaled'
    return config

## \brief Scale a model legacy ScattererConfiguration file.
#
#  \param config: `dict` A dictionary containing the script configuration.
#  \return errors: `int` The number of encountered errors.
def scale_legacy_edfb(config):
    errors = 0
    input_file = open(config['input_name'], 'r')
    output_file = open(config['output_name'], 'w')
    file_line = input_file.readline() # NSPH IES
    file_line = file_line.replace("D", "E").replace("d", "e")
    configurations = 0
    iter_numbers = int_reg.finditer(file_line)
    n_groups = []
    for ni in iter_numbers:
        n_groups.append(ni.group())
    nsph = int(n_groups[0])
    ies = int(n_groups[1])
    output_file.write(file_line)
    file_line = input_file.readline() # EXDC WP XIP IDFC NXI INSTPC INSN
    file_line = file_line.replace("D", "E").replace("d", "e")
    configurations = 0
    iter_numbers = float_reg.finditer(file_line)
    n_ends = []
    for ni in iter_numbers:
        n_ends.append(ni.end())
    iter_numbers = int_reg.finditer(file_line[n_ends[2]:])
    n_groups = []
    for ni in iter_numbers:
        n_groups.append(ni.group())
    nxi = int(n_groups[1])
    instpc = int(n_groups[2])
    output_file.write(file_line)
    # Read and copy the wavelength scales
    if (instpc == 1):
        file_line = input_file.readline()
        file_line = file_line.replace("D", "E").replace("d", "e")
        output_file.write(file_line)
    else:
        for fli in range(nxi):
            file_line = input_file.readline()
            file_line = file_line.replace("D", "E").replace("d", "e")
            output_file.write(file_line)
    # Read and copy the vector of sphere IDs
    vec_ids = []
    while (len(vec_ids) < nsph):
        file_line = input_file.readline()
        file_line = file_line.replace("D", "E").replace("d", "e")
        iter_numbers = int_reg.finditer(file_line)
        for ni in iter_numbers:
            vec_ids.append(int(ni.group()))
        output_file.write(file_line)
    configurations = 0
    for ci in range(len(vec_ids)):
        if (vec_ids[ci] > ci):
            configurations += 1
    # Read and scale the configuration size
    for ci in range(configurations):
        # work on a configuration ...
        file_line = input_file.readline()
        file_line = file_line.replace("D", "E").replace("d", "e")
        iter_numbers = int_reg.finditer(file_line)
        n_groups = []
        for ni in iter_numbers:
            n_groups.append(ni.group())
        num_layers = int(n_groups[0])
        iter_numbers = float_reg.finditer(file_line)
        n_groups = []
        for ni in iter_numbers:
            n_groups.append(ni.group())
        r_sph = float(n_groups[0]) * config['scale']
        str_line = "{0:3d}   {1:14.6e}\n".format(num_layers, r_sph)
        output_file.write(str_line)
        for cj in range(num_layers):
            file_line = input_file.readline()
            file_line = file_line.replace("D", "E").replace("d", "e")
            output_file.write(file_line)
    # Read and copy the optical constants
    file_line = input_file.readline()
    while(file_line != ""):
        file_line = file_line.replace("D", "E").replace("d", "e")
        output_file.write(file_line)
        file_line = input_file.readline()
    input_file.close()
    output_file.close()
    return errors

## \brief Scale a model legacy GeometryConfiguration file.
#
#  \param config: `dict` A dictionary containing the script configuration.
#  \return errors: `int` The number of encountered errors.
def scale_legacy_geom(config):
    errors = 0
    input_file = open(config['input_name'], 'r')
    output_file = open(config['output_name'], 'w')
    file_line = input_file.readline() # HEADER LINE
    iter_numbers = int_reg.finditer(file_line)
    n_groups = []
    for ni in iter_numbers:
        n_groups.append(ni.group())
    nsph = int(n_groups[0])
    output_file.write(file_line)
    for si in range(nsph):
        file_line = input_file.readline() # First data line
        file_line = file_line.replace("D", "E").replace("d", "e")
        iter_numbers = float_reg.finditer(file_line)
        n_groups = []
        for ni in iter_numbers:
            n_groups.append(ni.group())
        if (len(n_groups) == 3):
            # do it
            sph_x = float(n_groups[0]) * config['scale']
            sph_y = float(n_groups[1]) * config['scale']
            sph_z = float(n_groups[2]) * config['scale']
            str_line = "   {0:15.7e}   {1:15.7e}   {2:15.7e}\n".format(
                sph_x, sph_y, sph_z
            )
            output_file.write(str_line)
        else:
            print("ERROR: sphere coordinates vectors not in place!")
            errors += 1
            break # si loop on spheres
    file_line = input_file.readline()
    while (file_line != ""):
        file_line = file_line.replace("D", "E").replace("d", "e")
        output_file.write(file_line)
        file_line = input_file.readline()
    input_file.close()
    output_file.close()
    return errors

## \brief Print a command-line help summary.
def print_help():
    print("                                                  ")
    print("***                SCALE MODEL                 ***")
    print("                                                  ")
    print("Scale model input files by multiplicative factor. ")
    print("                                                             ")
    print("Usage: \"./scale_model.py --in INPUT --out OUTPUT [OPTIONS]\"")
    print("                                                             ")
    print("Valid options are:                                           ")
    print("--in INPUT               Original model to be scaled (mandatory).")
    print("--out OUTPUT             Name for the rescaled model (optional). ")
    print("--mode=[edfb|geom]       Type of input file to be processed (mandatory)")
    print("--scale=SCALE            Scale to be applied (optional, default is 1). ")
    print("--help                   Print this help and exit.                     ")
    print("                                                                       ")

# ### PROGRAM EXECUTION ###
## \cond
res = main()
## \endcond
exit(res)