Ejemplo n.º 1
0
 def __init__(self,
              name=None,
              subtitle='',
              fields=None,
              stride=None,
              record_images=False, record_data=False,
              base_dir='~/phi/data/',
              recorded_fields=None,
              summary=None,
              custom_properties=None,
              target_scene=None,
              objects_to_save=None,
              framerate=None):
     self.start_time = time.time()
     self.name = name if name is not None else self.__class__.__name__
     self.subtitle = subtitle
     self.summary = summary if summary else name
     if fields:
         self.fields = {name: TimeDependentField(name, generator) for (name, generator) in fields.items()}
     else:
         self.fields = {}
     self.message = None
     self.steps = 0
     self._invalidation_counter = 0
     self._controls = []
     self._actions = []
     self._traits = []
     self.prepared = False
     self.current_action = None
     self._pause = False
     self.detect_fields = 'default'  # False, True, 'default'
     self.world = world
     # Setup directory & Logging
     self.objects_to_save = [self.__class__] if objects_to_save is None else list(objects_to_save)
     self.base_dir = os.path.expanduser(base_dir)
     if not target_scene:
         self.new_scene()
         self.uses_existing_scene = False
     else:
         self.scene = target_scene
         self.uses_existing_scene = True
     if not isfile(self.scene.subpath('info.log')):
         log_file = self.log_file = self.scene.subpath('info.log')
     else:
         index = 2
         while True:
             log_file = self.scene.subpath('info_%d.log' % index)
             if not isfile(log_file):
                 break
             else:
                 index += 1
     # Setup logging
     logFormatter = logging.Formatter('%(message)s (%(levelname)s), %(asctime)sn\n')
     rootLogger = logging.getLogger()
     rootLogger.setLevel(logging.WARNING)
     customLogger = logging.Logger('app', logging.DEBUG)
     fileHandler = logging.FileHandler(log_file)
     fileHandler.setFormatter(logFormatter)
     customLogger.addHandler(fileHandler)
     consoleHandler = logging.StreamHandler(sys.stdout)
     consoleHandler.setFormatter(logFormatter)
     consoleHandler.setLevel(logging.INFO)
     customLogger.addHandler(consoleHandler)
     self.logger = customLogger
     # Recording
     self.record_images = record_images
     self.record_data = record_data
     self.recorded_fields = recorded_fields if recorded_fields is not None else []
     self.rec_all_slices = False
     self.sequence_stride = stride if stride is not None else 1
     self.framerate = framerate if framerate is not None else stride
     self._custom_properties = custom_properties if custom_properties else {}
     self.figures = PlotlyFigureBuilder()
     self.info('App created. Scene directory is %s' % self.scene.path)
Ejemplo n.º 2
0
class App(object):

    def __init__(self,
                 name=None,
                 subtitle='',
                 fields=None,
                 stride=None,
                 record_images=False, record_data=False,
                 base_dir='~/phi/data/',
                 recorded_fields=None,
                 summary=None,
                 custom_properties=None,
                 target_scene=None,
                 objects_to_save=None,
                 framerate=None):
        self.start_time = time.time()
        self.name = name if name is not None else self.__class__.__name__
        self.subtitle = subtitle
        self.summary = summary if summary else name
        if fields:
            self.fields = {name: TimeDependentField(name, generator) for (name, generator) in fields.items()}
        else:
            self.fields = {}
        self.message = None
        self.steps = 0
        self._invalidation_counter = 0
        self._controls = []
        self._actions = []
        self._traits = []
        self.prepared = False
        self.current_action = None
        self._pause = False
        self.detect_fields = 'default'  # False, True, 'default'
        self.world = world
        # Setup directory & Logging
        self.objects_to_save = [self.__class__] if objects_to_save is None else list(objects_to_save)
        self.base_dir = os.path.expanduser(base_dir)
        if not target_scene:
            self.new_scene()
            self.uses_existing_scene = False
        else:
            self.scene = target_scene
            self.uses_existing_scene = True
        if not isfile(self.scene.subpath('info.log')):
            log_file = self.log_file = self.scene.subpath('info.log')
        else:
            index = 2
            while True:
                log_file = self.scene.subpath('info_%d.log' % index)
                if not isfile(log_file):
                    break
                else:
                    index += 1
        # Setup logging
        logFormatter = logging.Formatter('%(message)s (%(levelname)s), %(asctime)sn\n')
        rootLogger = logging.getLogger()
        rootLogger.setLevel(logging.WARNING)
        customLogger = logging.Logger('app', logging.DEBUG)
        fileHandler = logging.FileHandler(log_file)
        fileHandler.setFormatter(logFormatter)
        customLogger.addHandler(fileHandler)
        consoleHandler = logging.StreamHandler(sys.stdout)
        consoleHandler.setFormatter(logFormatter)
        consoleHandler.setLevel(logging.INFO)
        customLogger.addHandler(consoleHandler)
        self.logger = customLogger
        # Recording
        self.record_images = record_images
        self.record_data = record_data
        self.recorded_fields = recorded_fields if recorded_fields is not None else []
        self.rec_all_slices = False
        self.sequence_stride = stride if stride is not None else 1
        self.framerate = framerate if framerate is not None else stride
        self._custom_properties = custom_properties if custom_properties else {}
        self.figures = PlotlyFigureBuilder()
        self.info('App created. Scene directory is %s' % self.scene.path)

    def new_scene(self, count=None):
        if count is None:
            count = 1 if self.world.batch_size is None else self.world.batch_size
        self.scene = Scene.create(self.base_dir, self.scene_summary(), count=count, mkdir=True)

    @property
    def directory(self):
        return self.scene.path

    @property
    def image_dir(self):
        return self.scene.subpath('images')

    def get_image_dir(self):
        return self.scene.subpath('images', create=True)

    def progress(self):
        self.step()
        self.steps += 1
        self.invalidate()

    def invalidate(self):
        self._invalidation_counter += 1

    def step(self):
        world.step()

    @property
    def fieldnames(self):
        return sorted(self.fields.keys())

    def get_field(self, fieldname):
        if fieldname not in self.fields:
            raise KeyError('Field %s not declared. Available fields are %s' % (fieldname, self.fields.keys()))
        return self.fields[fieldname].get(self._invalidation_counter)

    def add_field(self, name, value):
        assert not self.prepared, 'Cannot add fields to a prepared model'
        if isinstance(value, StateProxy):
            def current_state():
                return value.state
            generator = current_state
        elif callable(value):
            generator = value
        else:
            assert isinstance(value, (np.ndarray, Field, float, int)), 'Unsupported type for field "%s": %s' % (name, type(value))
            def get_constant():
                return value
            generator = get_constant
        self.fields[name] = TimeDependentField(name, generator)

    @property
    def actions(self):
        return self._actions

    def add_action(self, name, methodcall):
        self._actions.append(Action(name, methodcall, name))

    def run_action(self, action):
        message_before = self.message
        action.method()
        self.invalidate()
        message_after = self.message
        if message_before == message_after:
            if self.message is None or self.message == '':
                self.message = display_name(action.name)
            else:
                self.message += ' | ' + display_name(action.name)

    @property
    def traits(self):
        return self._traits

    def add_trait(self, trait):
        assert not self.prepared, 'Cannot add traits to a prepared model'
        self._traits.append(trait)

    @property
    def controls(self):
        return self._controls

    def prepare(self):
        if self.prepared:
            return
        logging.info('Gathering model data...')
        # Controls
        for name in self.__dict__:
            val = getattr(self, name)
            editable_value = None
            if isinstance(val, EditableValue):
                editable_value = val
                setattr(self, name, val.initial_value)  # Replace EditableValue with initial value
            elif name.startswith('value_'):
                value_name = display_name(name[6:])
                dtype = type(val)
                if dtype == bool:
                    editable_value = EditableBool(value_name, val)
                elif isinstance(val, numbers.Integral):  # Int
                    editable_value = EditableInt(value_name, val)
                elif isinstance(val, numbers.Number):  # Float
                    editable_value = EditableFloat(value_name, val)
                elif isinstance(val, six.string_types):
                    editable_value = EditableString(value_name, val)
            if editable_value:
                self._controls.append(Control(self, name, editable_value))
        # Actions
        for method_name in dir(self):
            if method_name.startswith('action_') and callable(getattr(self, method_name)):
                self._actions.append(Action(display_name(method_name[7:]), getattr(self, method_name), method_name))
        # Default fields
        if len(self.fields) == 0:
            self._add_default_fields()
        # Scene
        self._update_scene_properties()
        source_files_to_save = set()
        for object in self.objects_to_save:
            try:
                source_files_to_save.add(inspect.getabsfile(object))
            except TypeError:
                pass
        for source_file in source_files_to_save:
            self.scene.copy_src(source_file)
        # End
        self.prepared = True
        return self

    def _add_default_fields(self):
        def add_default_field(trace):
            field = trace.value
            if isinstance(field, (CenteredGrid, StaggeredGrid)):
                def field_generator():
                    world_state = self.world.state
                    return trace.find_in(world_state)
                self.add_field(field.name[0].upper() + field.name[1:], field_generator)
            return None
        struct.map(add_default_field, self.world.state, leaf_condition=lambda x: isinstance(x, (CenteredGrid, StaggeredGrid)), trace=True, content_type=struct.INVALID)

    def add_custom_property(self, key, value):
        self._custom_properties[key] = value
        if self.prepared:
            self._update_scene_properties()

    def add_custom_properties(self, dictionary):
        self._custom_properties.update(dictionary)
        if self.prepared:
            self._update_scene_properties()

    def _update_scene_properties(self):
        if self.uses_existing_scene:
            return
        try:
            app_name = os.path.basename(inspect.getfile(self.__class__))
            app_path = inspect.getabsfile(self.__class__)
        except TypeError:
            app_name = app_path = ''
        properties = {
            'instigator': 'App',
            'traits': self.traits,
            'app': str(app_name),
            'app_path': str(app_path),
            'name': self.name,
            'description': self.subtitle,
            'all_fields': self.fieldnames,
            'actions': [action.name for action in self.actions],
            'controls': [{control.name: control.value} for control in self.controls],
            'summary': self.scene_summary(),
            'time_of_writing': self.steps,
            'world': struct.properties_dict(self.world.state)
        }
        properties.update(self.custom_properties())
        self.scene.properties = properties

    def settings_str(self):
        return ''.join([
            ' ' + str(control) for control in self.controls
        ])

    def custom_properties(self):
        return self._custom_properties

    def info(self, message):
        message = str(message)
        self.message = message
        self.logger.info(message)

    def debug(self, message):
        logging.info(message)

    def scene_summary(self):
        return self.summary

    def show(self, *args, **kwargs):
        warnings.warn("Use show(model) instead.", DeprecationWarning, stacklevel=2)
        from phi.viz.display import show
        show(self, *args, **kwargs)

    @property
    def status(self):
        pausing = '/Pausing' if self._pause and self.current_action else ''
        action = self.current_action if self.current_action else 'Idle'
        message = (' - %s' % self.message) if self.message else ''
        return '{}{} ({}){}'.format(action, pausing, self.steps, message)

    def run_step(self, framerate=None, allow_recording=True):
        self.current_action = 'Running'
        starttime = time.time()
        try:
            self.progress()
            if allow_recording and self.steps % self.sequence_stride == 0:
                self.record_frame()
            if framerate is not None:
                duration = time.time() - starttime
                rest = 1.0/framerate - duration
                if rest > 0:
                    self.current_action = 'Waiting'
                    time.sleep(rest)
        except Exception as e:
            self.info('Error during %s.step() \n %s: %s' % (type(self).__name__, type(e).__name__, e))
            self.logger.exception(e)
        finally:
            self.current_action = None

    def play(self, max_steps=None, callback=None, framerate=None, allow_recording=True, callback_if_aborted=False):
        if framerate is None:
            framerate = self.framerate
        def target():
            self._pause = False
            step_count = 0
            while not self._pause:
                self.run_step(framerate=framerate, allow_recording=allow_recording)
                step_count += 1
                if max_steps and step_count >= max_steps:
                    break
            if callback is not None:
                if not self._pause or callback_if_aborted:
                    callback()

        thread = threading.Thread(target=target)
        thread.start()
        return self

    def pause(self):
        self._pause = True

    @property
    def running(self):
        return self.current_action is not None

    def record_frame(self):
        self.current_action = 'Recording'
        files = []

        if self.record_images:
            os.path.isdir(self.image_dir) or os.makedirs(self.image_dir)
            arrays = [self.get_field(field) for field in self.recorded_fields]
            for name, array in zip(self.recorded_fields, arrays):
                files += self.figures.save_figures(self.image_dir, name, self.steps, array)

        if self.record_data:
            arrays = [self.get_field(field) for field in self.recorded_fields]
            arrays = [a.staggered_tensor() if isinstance(a, StaggeredGrid) else a.data for a in arrays]
            names = [n.lower() for n in self.recorded_fields]
            files += write_sim_frame(self.directory, arrays, names, self.steps)

        if files:
            self.message = 'Frame written to %s' % files
        self.current_action = None

    def benchmark(self, sequence_count):
        self._pause = False
        step_count = 0
        starttime = time.time()
        for i in range(sequence_count):
            self.run_step(framerate=np.inf, allow_recording=False)
            step_count += 1
            if self._pause:
                break
        time_elapsed = time.time() - starttime
        return step_count, time_elapsed

    def config_recording(self, images, data, fields):
        self.record_images = images
        self.record_data = data
        self.recorded_fields = fields
Ejemplo n.º 3
0
 def __init__(self,
              name="Φ-*flow* Application",
              subtitle="Interactive demo based on PhiFlow",
              fields=None,
              stride=1,
              record_images=False,
              record_data=False,
              base_dir=os.path.expanduser(os.path.join("~", "model")),
              recorded_fields=None,
              summary=None,
              custom_properties=None,
              target_scene=None,
              objects_to_save=None):
     self.name = name
     self.subtitle = subtitle
     self.summary = summary if summary else name
     if fields:
         self.fields = {
             name: TimeDependentField(name, generator)
             for (name, generator) in fields.items()
         }
     else:
         self.fields = {}
     self.message = None
     self.time = 0
     self._invalidation_counter = 0
     self.print_to_console = True
     self._controls = []
     self._actions = []
     self._traits = []
     self.prepared = False
     self.current_action = None
     self._pause = False
     # Setup directory & Logging
     self.objects_to_save = [
         self.__class__
     ] if objects_to_save is None else list(objects_to_save)
     self.base_dir = os.path.expanduser(base_dir)
     if not target_scene:
         self.new_scene()
         self.uses_existing_scene = False
     else:
         self.scene = target_scene
         self.uses_existing_scene = True
     if not isfile(self.scene.subpath("info.log")):
         logfile = self.scene.subpath("info.log")
     else:
         index = 2
         while True:
             logfile = self.scene.subpath("info_%d.log" % index)
             if not isfile(logfile): break
             else: index += 1
     logging.basicConfig(
         filename=logfile,
         level=logging.INFO,
         format="%(message)s (%(levelname)s), %(asctime)sn\n")
     print("Scene directory is %s" % self.scene.path)
     # Recording
     self.record_images = record_images
     self.record_data = record_data
     self.recorded_fields = recorded_fields if recorded_fields is not None else []
     self.rec_all_slices = False
     self.sequence_stride = stride
     self._custom_properties = custom_properties if custom_properties else {}
     self.figures = PlotlyFigureBuilder()
     self._simulation = None
     self.info("Setting up model...")
Ejemplo n.º 4
0
                                               (y - 10)**2) / 32**2)
        target_velocity_y[:, 0:32] = 0
        target_velocity = math.expand_dims(
            np.stack([target_velocity_y,
                      np.zeros_like(target_velocity_y)],
                     axis=-1), 0)
        target_velocity *= self.editable_int('Target_Direction', 1, [-1, 1])

        # Optimization
        loss = math.l2_loss(velocity.staggered_tensor()[:, :, 31:, :] -
                            target_velocity[:, :, 31:, :])
        self.add_objective(loss, 'Loss')

        gradient = StaggeredGrid(
            tf.gradients(
                loss,
                [field.data for field in optimizable_velocity.unstack()]))

        self.add_field('Initial Velocity (Optimizable)', optimizable_velocity)
        self.add_field('Gradient', gradient)
        self.add_field('Final Velocity', velocity)
        self.add_field('Target Velocity', target_velocity)

        self.custom_stride = 100


show(display=('Final Velocity', 'Target Velocity'),
     figure_builder=PlotlyFigureBuilder(batches=[0],
                                        depths=[0],
                                        component='vec2'))
Ejemplo n.º 5
0
class FieldSequenceModel(object):
    def __init__(self,
                 name="Φ-*flow* Application",
                 subtitle="Interactive demo based on PhiFlow",
                 fields=None,
                 stride=1,
                 record_images=False,
                 record_data=False,
                 base_dir=os.path.expanduser(os.path.join("~", "model")),
                 recorded_fields=None,
                 summary=None,
                 custom_properties=None,
                 target_scene=None,
                 objects_to_save=None):
        self.name = name
        self.subtitle = subtitle
        self.summary = summary if summary else name
        if fields:
            self.fields = {
                name: TimeDependentField(name, generator)
                for (name, generator) in fields.items()
            }
        else:
            self.fields = {}
        self.message = None
        self.time = 0
        self._invalidation_counter = 0
        self.print_to_console = True
        self._controls = []
        self._actions = []
        self._traits = []
        self.prepared = False
        self.current_action = None
        self._pause = False
        # Setup directory & Logging
        self.objects_to_save = [
            self.__class__
        ] if objects_to_save is None else list(objects_to_save)
        self.base_dir = os.path.expanduser(base_dir)
        if not target_scene:
            self.new_scene()
            self.uses_existing_scene = False
        else:
            self.scene = target_scene
            self.uses_existing_scene = True
        if not isfile(self.scene.subpath("info.log")):
            logfile = self.scene.subpath("info.log")
        else:
            index = 2
            while True:
                logfile = self.scene.subpath("info_%d.log" % index)
                if not isfile(logfile): break
                else: index += 1
        logging.basicConfig(
            filename=logfile,
            level=logging.INFO,
            format="%(message)s (%(levelname)s), %(asctime)sn\n")
        print("Scene directory is %s" % self.scene.path)
        # Recording
        self.record_images = record_images
        self.record_data = record_data
        self.recorded_fields = recorded_fields if recorded_fields is not None else []
        self.rec_all_slices = False
        self.sequence_stride = stride
        self._custom_properties = custom_properties if custom_properties else {}
        self.figures = PlotlyFigureBuilder()
        self._simulation = None
        self.info("Setting up model...")

    def new_scene(self):
        self.scene = phi.fluidformat.new_scene(self.base_dir,
                                               self.scene_summary(),
                                               mkdir=True)

    @property
    def sim(self):
        return self._simulation

    @sim.setter
    def sim(self, sim):
        self._simulation = sim

    def set_simulation(self, sim):
        self.sim = sim

    @property
    def directory(self):
        return self.scene.path

    @property
    def image_dir(self):
        return self.scene.subpath("images")

    def get_image_dir(self):
        return self.scene.subpath("images", create=True)

    def progress(self):
        self.time += 1
        self.step()
        self.invalidate()

    def invalidate(self):
        self._invalidation_counter += 1

    def step(self):
        self.info("Implement step(self) to have something happen")

    @property
    def fieldnames(self):
        return sorted(self.fields.keys())

    def get_field(self, fieldname):
        if not fieldname in self.fields:
            raise KeyError("Field %s not declared. Available fields are %s" %
                           (fieldname, self.fields.keys()))
        return self.fields[fieldname].get(self._invalidation_counter)

    def add_field(self, name, generator):
        assert not self.prepared, "Cannot add fields to a prepared model"
        self.fields[name] = TimeDependentField(name, generator)

    @property
    def actions(self):
        return self._actions

    def add_action(self, name, methodcall):
        self._actions.append(Action(name, methodcall, name))

    def run_action(self, action):
        message_before = self.message
        action.method()
        self.invalidate()
        message_after = self.message
        if message_before == message_after:
            if self.message is None or self.message == "":
                self.message = display_name(action.name)
            else:
                self.message += " | " + display_name(action.name)

    @property
    def traits(self):
        return self._traits

    def add_trait(self, trait):
        assert not self.prepared, "Cannot add traits to a prepared model"
        self._traits.append(trait)

    @property
    def controls(self):
        return self._controls

    def prepare(self):
        if self.prepared:
            return
        logging.info("Gathering model data...")
        self.prepared = True
        # Controls
        for name in dir(self):
            val = getattr(self, name)
            editable_value = None
            if isinstance(val, EditableValue):
                editable_value = val
                setattr(self, name, val.initial_value
                        )  # Replace EditableValue with initial value
            elif name.startswith("value_"):
                value_name = display_name(name[6:])
                dtype = type(val)
                if dtype == bool:
                    editable_value = EditableBool(value_name, val)
                elif isinstance(val, numbers.Integral):  # Int
                    editable_value = EditableInt(value_name, val)
                elif isinstance(val, numbers.Number):  # Float
                    editable_value = EditableFloat(value_name, val)
                elif isinstance(val, six.string_types):
                    editable_value = EditableString(value_name, val)
            if editable_value:
                self._controls.append(Control(self, name, editable_value))
        # Actions
        for method_name in dir(self):
            if method_name.startswith("action_") and callable(
                    getattr(self, method_name)):
                self._actions.append(
                    Action(display_name(method_name[7:]),
                           getattr(self, method_name), method_name))
        # Scene
        self._update_scene_properties()
        source_files_to_save = set()
        for object in self.objects_to_save:
            source_files_to_save.add(inspect.getabsfile(object))
        for source_file in source_files_to_save:
            self.scene.copy_src(source_file)

    def add_custom_property(self, key, value):
        self._custom_properties[key] = value
        if self.prepared: self._update_scene_properties()

    def add_custom_properties(self, dict):
        self._custom_properties.update(dict)
        if self.prepared: self._update_scene_properties()

    def _update_scene_properties(self):
        if self.uses_existing_scene: return
        app_name = inspect.getfile(self.__class__)
        app_path = inspect.getabsfile(self.__class__)
        properties = {
            "instigator":
            "FieldSequenceModel",
            "traits":
            self.traits,
            "app":
            str(app_name),
            "app_path":
            str(app_path),
            "name":
            self.name,
            "description":
            self.subtitle,
            "all_fields":
            self.fieldnames,
            "actions": [action.name for action in self.actions],
            "controls": [{
                control.name: control.value
            } for control in self.controls],
            "summary":
            self.scene_summary(),
            "time_of_writing":
            self.time,
        }
        if self._simulation:
            properties.update(self._simulation.as_dict())
        properties.update(self.custom_properties())
        self.scene.properties = properties

    def settings_str(self):
        return "".join([" " + str(control) for control in self.controls])

    def custom_properties(self):
        return self._custom_properties

    def info(self, message):
        if isinstance(message, int):
            self.time = message
        else:
            self.message = message
            if self.print_to_console:
                print(str(self.time) + ": " + message)
            logging.info(message)

    def debug(self, message):
        logging.info(message)

    def scene_summary(self):
        return self.summary

    def list_controls(self, names):
        return

    def show(self, *args, **kwargs):
        from phi.viz.dash_gui import DashFieldSequenceGui
        gui = DashFieldSequenceGui(self, *args, **kwargs)
        return gui.show()

    @property
    def status(self):
        pausing = "/Pausing" if self._pause and self.current_action else ""
        action = self.current_action if self.current_action else "Idle"
        message = (" - %s" % self.message) if self.message else ""
        return "{}{} ({}){}".format(action, pausing, self.time, message)

    def run_step(self, framerate=None, allow_recording=True):
        try:
            self.current_action = "Running"
            starttime = time.time()
            self.progress()
            if allow_recording and self.time % self.sequence_stride == 0:
                self.record_frame()
            if framerate is not None:
                duration = time.time() - starttime
                rest = 1.0 / framerate / self.sequence_stride - duration
                if rest > 0:
                    self.current_action = "Waiting"
                    time.sleep(rest)
        finally:
            self.current_action = None

    def play(self,
             max_steps=None,
             callback=None,
             framerate=None,
             allow_recording=True):
        def target():
            self._pause = False
            step_count = 0
            while not self._pause:
                self.run_step(framerate=framerate,
                              allow_recording=allow_recording)
                step_count += 1
                if max_steps and step_count >= max_steps:
                    break
            if callback is not None:
                callback()

        thread = threading.Thread(target=target)
        thread.start()
        return self

    def pause(self):
        self._pause = True

    @property
    def running(self):
        return self.current_action is not None

    def record_frame(self):
        self.current_action = "Recording"
        files = []

        if self.record_images:
            os.path.isdir(self.image_dir) or os.makedirs(self.image_dir)
            arrays = [self.get_field(field) for field in self.recorded_fields]
            for name, array in zip(self.recorded_fields, arrays):
                files += self.figures.save_figures(self.image_dir, name,
                                                   self.time, array)

        if self.record_data:
            arrays = [self.get_field(field) for field in self.recorded_fields]
            arrays = [
                a.staggered if isinstance(a, phi.math.nd.StaggeredGrid) else a
                for a in arrays
            ]
            files += phi.fluidformat.write_sim_frame(self.directory, arrays,
                                                     self.recorded_fields,
                                                     self.time)

        if files:
            self.message = "Frame written to %s" % files
        self.current_action = None

    def benchmark(self, sequence_count):
        self._pause = False
        step_count = 0
        starttime = time.time()
        for i in range(sequence_count):
            self.run_step(framerate=None, allow_recording=False)
            step_count += 1
            if self._pause: break
        time_elapsed = time.time() - starttime
        return step_count, time_elapsed

    def config_recording(self, images, data, fields):
        self.record_images = images
        self.record_data = data
        self.recorded_fields = fields