Custom Parsing¶
This notebook describes the process for implementing handling of custom input files for lume.base.Base subclasses.
LUME-base objects can be instantiated using a single YAML file or a series of YAML files defining configuration options. The top-level file defines the base configuration options:
- input_file
- initial_particles
- verbose
- timeout
import inspect
# Print code
from IPython.display import Markdown, display
from lume.base import Base
def sdisplay(obj):
spec = inspect.getsource(obj)
display(Markdown(f"```python \n {spec} \n ```"))
The from_yaml class method may be used to instantiate the Base subclass using the top-level configuration file. The from_yaml method calls the abstract, static parse_input method, which must be implemented by the developer to handle input parsing for their implementation.
sdisplay(Base.from_yaml)
@classmethod
def from_yaml(cls, yaml_file):
"""
Returns an object instantiated from a YAML config file
Will load intial_particles from an h5 file.
"""
# Try file
if os.path.exists(tools.full_path(yaml_file)):
yaml_file = tools.full_path(yaml_file)
config = yaml.safe_load(open(yaml_file))
if 'input_file' in config:
# Check that the input file is absolute path...
# require absolute/ relative to working dir for model input file
f = os.path.expandvars(config['input_file'])
if not os.path.isabs(f):
# Get the yaml file root
root, _ = os.path.split(tools.full_path(yaml_file))
config['input_file'] = os.path.join(root, f)
# Here, we update the config with the input_file contents
# provided that the input_parser method has been implemented on the subclass
parsed_input = cls.input_parser(config['input_file'])
config.update(parsed_input)
else:
# Try raw string
config = yaml.safe_load(yaml_file)
if "input_file" in config:
parsed_input = cls.input_parser(config['input_file'])
config.update(parsed_input)
# Form ParticleGroup from file
if 'initial_particles' in config:
f = config['initial_particles']
if not os.path.isabs(f):
root, _ = os.path.split(tools.full_path(yaml_file))
f = os.path.join(root, f)
config['initial_particles'] = ParticleGroup(f)
return cls(**config)
MyModel implements the parse_input method and adds placeholders for other abstract methods:
from lume.tests.files import INPUT_YAML, LUME_CONFIG_YAML
from lume.tests.files.test_command_wrapper_subclass import MyModel
sdisplay(MyModel)
class MyModel(Base):
def __init__(self, *args, variables=None, input_image=None, **kwargs):
super().__init__(*args, **kwargs)
self._input_image = input_image
self._variables = variables
#implementation of abstract method
@staticmethod
def input_parser(path):
config = {}
if os.path.exists(tools.full_path(path)):
yaml_file = tools.full_path(path)
config = yaml.safe_load(open(yaml_file))
if "input_image" in config:
# check if input image full path provided
if os.path.exists(tools.full_path(config["input_image"])):
input_image_path = tools.full_path(config["input_image"])
# if not a full path, compose path relative to the yaml file directory
else:
root, _ = os.path.split(tools.full_path(path))
input_image_path = os.path.join(root, config["input_image"])
if not os.path.exists(tools.full_path(input_image_path)):
raise Exception("Unable to resolve input impage path %s", input_image_path)
config["input_image"] = np.load(input_image_path)
else:
raise Exception("Unable to parse model input file path %s", path)
return config
def archive(self):
...
def configure(self):
...
def load_archive(self):
...
def load_output(self):
...
def plot(self):
...
def run(self):
...
def write_input(self):
...
In this case, out configuration file looks like:
with open(LUME_CONFIG_YAML) as stream:
print(stream.read())
input_file: test_input_file.yml timeout: 100 verbose: true
And the input_file looks like:
with open(INPUT_YAML) as stream:
print(stream.read())
input_image: test_input_image.npy
variables:
variable_1:
value: 1
variable_2:
value: 2
Instantiate model:
MyModel = MyModel.from_yaml(LUME_CONFIG_YAML)
MyModel
<lume.tests.files.test_command_wrapper_subclass.MyModel at 0x7fcbe04fa280>
# input image
MyModel._input_image
array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]])
# variables
MyModel._variables
{'variable_1': {'value': 1}, 'variable_2': {'value': 2}}