{"id":262,"date":"2023-06-09T16:29:22","date_gmt":"2023-06-09T14:29:22","guid":{"rendered":"https:\/\/logbooks.ifosim.org\/finesse\/?p=262"},"modified":"2023-06-09T16:29:22","modified_gmt":"2023-06-09T14:29:22","slug":"writing-your-own-action","status":"publish","type":"post","link":"https:\/\/logbooks.ifosim.org\/finesse\/2023\/06\/09\/writing-your-own-action\/","title":{"rendered":"Writing your own action"},"content":{"rendered":"\n<p>Finesse3 provides multiple Actions to interact with a model. But maybe you want to do something no one else has thought of. In this case you can add your own Action to finesse. That way it can be easily used in any model, shared with other users and maybe even added to finesse itself.<\/p>\n\n\n\n<p>All actions start with <a href=\"https:\/\/finesse.ifosim.org\/docs\/latest\/_modules\/finesse\/analysis\/actions\/base.html#Action\" data-type=\"URL\" data-id=\"https:\/\/finesse.ifosim.org\/docs\/latest\/_modules\/finesse\/analysis\/actions\/base.html#Action\" target=\"_blank\" rel=\"noreferrer noopener\">finesse.analysis.actions.Action<\/a>. It is a metaclass all Actions should inherit from. And it tells us exactly what we need to implement for our Action to work: We need to implement the functions left as abstract in <code>Action<\/code>: <code>_requests<\/code> and <code>_do<\/code>.<\/p>\n\n\n\n<p>The first step is to create a new class<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from finesse.analysis.action import Action\n\nclass MyAction(Action):\n        def __init__(self, some_argument, name=\"myaction\"):\n                super().__init__(name)\n                # do some other initialsation\n                self.arg = some_argument<\/code><\/pre>\n\n\n\n<p>_requests informs finesse about what parameters your action needs to be included in the simulation. The docstring tells us the details:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>def _requests(self, model, memo, first=True):\n        \"\"\"\n        Updates the memo dictionary with details about what this action needs from a\n        simulation to run. Parent actions will get requests from all its child actions\n        so that it can build a model that suits all of them, to minimise the amount of\n        building.\n\n        This method can do initial checks to make sure the model has the\n        required features to perform the action too.\n\n        memo&#091;'changing_parameters'] - append to this list the full name string\n                                      of parameters that this action needs\n        memo&#091;'keep_nodes'] - append to this list the full name string\n                                    of nodes that this action needs to keep.\n                                    This should be used where actions are\n                                    accessing node outputs without using a\n                                    detector element (which registers that\n                                    nodes should be kept already).\n\n        Parameters\n        ----------\n        model : Model\n            The Model that the action will be operating on\n        memo : defaultdict(list)\n            A dictionary that should be filled with requests\n        first : boolean\n            True if this is the first request being made\n        \"\"\"<\/code><\/pre>\n\n\n\n<p>So we get the model object, a list to write our parameters to and a flag whether we&#8217;re the first action. If you want to add a parameter <code>p<\/code> to <code>memo<\/code> the syntax is<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>memo&#091;\"changing_parameter\"].extend(p)<\/code><\/pre>\n\n\n\n<p>If your action calls other actions you need to call their <code>_requests<\/code> as well. This is also a good point to check whether your action can actually be run on the model. If it is in some bad state its better to raise an Exception here than wait for things to go wrong during the simulation. This can save a lot of time while debugging your simulation.<\/p>\n\n\n\n<p>The second function you need to include is <code>_do<\/code>. This is where your action actually does something. It gets called with one argument: <a rel=\"noreferrer noopener\" href=\"https:\/\/finesse.ifosim.org\/docs\/latest\/api\/analysis\/actions\/base\/finesse.analysis.actions.base.AnalysisState.html#finesse.analysis.actions.base.AnalysisState\" data-type=\"URL\" data-id=\"https:\/\/finesse.ifosim.org\/docs\/latest\/api\/analysis\/actions\/base\/finesse.analysis.actions.base.AnalysisState.html#finesse.analysis.actions.base.AnalysisState\" target=\"_blank\">AnalysisState<\/a>. It includes a lot of information about the simulation for you to use, look it up in the documentation.<\/p>\n\n\n\n<p><code>_do<\/code> needs to return some kind of Solution, that is an object that inherits from <a rel=\"noreferrer noopener\" href=\"https:\/\/finesse.ifosim.org\/docs\/latest\/api\/solutions\/base\/finesse.solutions.base.BaseSolution.html\" data-type=\"URL\" data-id=\"https:\/\/finesse.ifosim.org\/docs\/latest\/api\/solutions\/base\/finesse.solutions.base.BaseSolution.html\" target=\"_blank\">BaseSolution<\/a>. You can create your own solution class tailored to your action, but you can also use finesse.solutions.SimpleSolution:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from finesse.solutions import SimpleSolution\n\ndef _do(self, state):\n        sol = SimpleSolution(self.name)\n        # calculate something\n        result = self._some_func(state.model)\n        sol.children.append(result)\n        return sol<\/code><\/pre>\n\n\n\n<p>A class defined like this can just be used in the python API with finesse.model.run or inside other actions like Series. They behave like any standard action.<\/p>\n\n\n\n<p>But maybe you also want to call your action from KatScript. This is easily doable: <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from finesse.script.spec import make_analysis, KATSPEC\nadapter = make_analysis(MyAction, \"myaction\")\nKATSPEC.register_analysis(adapter)<\/code><\/pre>\n\n\n\n<p>In the first step a KatScript adapter is created from the Action, this is what can create the action from a KatScript string. The second line then adds this adapter to the default KatScript specification. Note that by default no command can be overwritten, if the name already exists trying to register it will throw an exception. If you do want to overwrite a command, use <code>overwrite=True<\/code><\/p>\n\n\n\n<p>There are limitations to this: for an action to work in KatScript, it must be possible to specify its arguments in KatScript. This is possible for model parameters, integers, lists and several other datatypes, but of course not for every possible one: Everyone can create their own type and even some built-in types cannot be written in KatScript. This is either because they just are not implemented or because their usual syntax would collide with KatScript syntax. This is one of the reasons some actions only exist in the python API but not in the KatScript.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Example<\/h2>\n\n\n\n<p>Lets create a simple example action. It will just print a message when run.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from finesse.analysis.actions import Action\nfrom finesse.solutions import SimpleSolution\n\nclass MessageAction(Action):\n    def __init__(self, message=None, name=\"messageaction\"):\n        super().__init__(name)\n        self._message = message\n    \n    def _requests(self, model, memo, first=True):\n        pass\n    \n    def _do(self, state):\n        print(f\"{state.model} says {self._message}\")\n        return SimpleSolution(self.name)<\/code><\/pre>\n\n\n\n<p>Now we can run<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import finesse\n\nmodel = finesse.Model()\nmodel.parse(\n    \"\"\"\n    laser l1 P=0\n    \"\"\"\n)\nsol = model.run(MessageAction(\"Hello World!))<\/code><\/pre>\n\n\n\n<p>which prints the following line to stdout. Note that sol contains no useful data.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;finesse.model.Model object at 0x7f9c78877460&gt; says Hello World!<\/code><\/pre>\n\n\n\n<p>To add it to KatScript:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from finesse.script.spec import make_analysis, KATSPEC\n\nKATSPEC.register_analysis(make_analysis(MessageAction, \"messageaction\"))<\/code><\/pre>\n\n\n\n<p>which allows<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>model = finesse.Model()\nmodel.parse(\n    \"\"\"\n    laser l1 P=0\n    messageaction(\"Hello World!\")\n    \"\"\"\n)\nsol = m.run()<\/code><\/pre>\n\n\n\n<p>producing the same result as before.<\/p>\n\n\n\n<p>To learn more about how actions work it can help to look at the source of the existing actions. Good starting points are <a rel=\"noreferrer noopener\" href=\"https:\/\/finesse.ifosim.org\/docs\/latest\/_modules\/finesse\/analysis\/actions\/sweep.html#Sweep\" data-type=\"URL\" data-id=\"https:\/\/finesse.ifosim.org\/docs\/latest\/_modules\/finesse\/analysis\/actions\/sweep.html#Sweep\" target=\"_blank\">Sweep<\/a> and <a rel=\"noreferrer noopener\" href=\"https:\/\/finesse.ifosim.org\/docs\/latest\/_modules\/finesse\/analysis\/actions\/random.html\" data-type=\"URL\" data-id=\"https:\/\/finesse.ifosim.org\/docs\/latest\/_modules\/finesse\/analysis\/actions\/random.html\" target=\"_blank\">random<\/a>. The first is the basic action for scanning a parameter and is a good example for writing <code>_requests<\/code>. The second contains several minimalistic actions that are easier to understand than <code>Sweep<\/code>, which does most of its actual action in a cpython function for performance reasons. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Finesse3 provides multiple Actions to interact with a model. But maybe you want to do something no one else has thought of. In this case you can add your own Action to finesse. That way it can be easily used in any model, shared with other users and maybe even added to finesse itself. All [&hellip;]<\/p>\n","protected":false},"author":73,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"ssl_alp_hide_revisions":false,"footnotes":"","ssl_alp_hide_crossreferences_to":false},"categories":[1],"tags":[],"ssl-alp-coauthor":[42],"class_list":["post-262","post","type-post","status-publish","format-standard","hentry","category-uncategorised"],"_links":{"self":[{"href":"https:\/\/logbooks.ifosim.org\/finesse\/wp-json\/wp\/v2\/posts\/262","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/logbooks.ifosim.org\/finesse\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/logbooks.ifosim.org\/finesse\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/logbooks.ifosim.org\/finesse\/wp-json\/wp\/v2\/users\/73"}],"replies":[{"embeddable":true,"href":"https:\/\/logbooks.ifosim.org\/finesse\/wp-json\/wp\/v2\/comments?post=262"}],"version-history":[{"count":3,"href":"https:\/\/logbooks.ifosim.org\/finesse\/wp-json\/wp\/v2\/posts\/262\/revisions"}],"predecessor-version":[{"id":306,"href":"https:\/\/logbooks.ifosim.org\/finesse\/wp-json\/wp\/v2\/posts\/262\/revisions\/306"}],"wp:attachment":[{"href":"https:\/\/logbooks.ifosim.org\/finesse\/wp-json\/wp\/v2\/media?parent=262"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/logbooks.ifosim.org\/finesse\/wp-json\/wp\/v2\/categories?post=262"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/logbooks.ifosim.org\/finesse\/wp-json\/wp\/v2\/tags?post=262"},{"taxonomy":"ssl-alp-coauthor","embeddable":true,"href":"https:\/\/logbooks.ifosim.org\/finesse\/wp-json\/wp\/v2\/ssl-alp-coauthor?post=262"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}