<jupyter_start><jupyter_text>Custom Action Features Overview Questions- How do I access simulation state information?- How do I create loggable quantities in custom actions?- What are other features provided by the custom action/operation  API? Objectives- Explain how to access simulation state in a custom action.- Explain how to expose loggable quantities in a custom action.- Demonstrate other miscellaneous features of custom actions. Boilerplate Code<jupyter_code>import hoomd

cpu = hoomd.device.CPU()
sim = hoomd.Simulation(device=cpu)

snap = hoomd.Snapshot()
snap.particles.N = 1
snap.particles.position[:] = [0, 0, 0]
snap.particles.types = ['A']
snap.particles.typeid[:] = [0]
snap.configuration.box = [10, 10, 10, 0, 0, 0]

sim.create_state_from_snapshot(snap)<jupyter_output><empty_output><jupyter_text>How do I access simulation state?By the time that a custom action will have its `act` method calledit will have an attribute `_state` accessible to it which is thesimulation state for the simulation it is associated with. The behaviorof this is controlled in the `hoomd.custom.Action.attach` method. Themethod takes in a simulation object and performs any necessary set-upfor the action call `act`. By default, the method stores the simulationstate in the `_state` attribute.We will create two custom actions class to show this. In one, we willnot modify the `attach` method, and in the other we will make `attach`method also print out some information.<jupyter_code>class PrintTimestep(hoomd.custom.Action):

    def act(self, timestep):
        print(timestep)


class NotifyAttachWithPrint(hoomd.custom.Action):

    def attach(self, simulation):
        print(f"Has '_state' attribute {hasattr(self, '_state')}.")
        super().attach(simulation)
        print(f"Has '_state' attribute {hasattr(self, '_state')}.")

    def act(self, timestep):
        print(timestep)<jupyter_output><empty_output><jupyter_text>Like in the previous section these are both writers. We will go aheadand wrap them and see what happens when we try to run the simulation.<jupyter_code>print_timestep = PrintTimestep()
print_timestep_operation = hoomd.write.CustomWriter(
    action=print_timestep, trigger=hoomd.trigger.Periodic(10))
sim.operations.writers.append(print_timestep_operation)
sim.run(0)
sim.operations -= print_timestep_operation
print_timestep_with_notify = NotifyAttachWithPrint()
sim.operations.writers.append(
    hoomd.write.CustomWriter(action=print_timestep_with_notify,
                             trigger=hoomd.trigger.Periodic(10)))
sim.run(0)<jupyter_output>Has '_state' attribute False.
Has '_state' attribute True.<jupyter_text>Loggable Quantities in Custom ActionsCustom actions can hook into HOOMD-blue's logging subsystem by usingthe `hoomd.logging.log` decorator to document which methods/propertiesof a custom action are loggable. See the documentation on `hoomd.logging.log` and `hoomd.logging.TypeFlags` for complete documenation of the decorator and loggable types.In general, `log` as a decorator takes optional arguments that control whether to make a method a property, what type the loggable quantity is, and whether the quantity should be logged by default.Rather than elaborate, we will use an example to explain these attributes.<jupyter_code>class ActionWithLoggables(hoomd.custom.Action):

    @hoomd.logging.log
    def scalar_property_loggable(self):
        return 42

    @hoomd.logging.log(category='string')
    def string_loggable(self):
        return "I am a string loggable."

    def act(self, timestep):
        pass


action = ActionWithLoggables()
action.scalar_property_loggable
action.string_loggable<jupyter_output><empty_output><jupyter_text>Custom Operation WrappingAnother feature of the custom action API is that when an object iswrapped by a custom operation object (which is necessary to add acustom action to a simulation), the action's attributes are available through the operation object as if the operation were theaction. For example, we will wrap `action` from the previous code blockin a `CustomWriter` and access its attributes that way.Due to this wrapping the attribute `trigger` should not exist in yourcustom action.<jupyter_code>custom_op = hoomd.write.CustomWriter(action=action, trigger=100)
custom_op.scalar_property_loggable
custom_op.string_loggable<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07646520146520147, 'fraction_numerical': 0.005723443223443223, 'mean_word_length': 5.2682926829268295, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 20, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '365668', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 1280, 'n_tokens_neox': 1239, 'n_words': 444, 'path': '04-Custom-Actions-In-Python/03-Custom-Action-Features.ipynb', 'repo_name': 'glotzerlab/hoomd-examples', 'size': '4368'}