@@ -181,7 +181,9 @@ are of unit length. See relevant `BOUT++ docs
181181<https://bout-dev.readthedocs.io/en/stable/developer_docs/data_types.html> `_
182182for more info. There is also a data type called ``Options `` which is equivalent
183183to a Python dictionary with extra functionality, and is used to store input
184- options, the entire simulation state and many other data.
184+ options, the entire simulation state and many other data. Finally,
185+ there is the ``GuardedOptions `` datatype, which wraps an ``Options ``
186+ object and controls access to its contents.
185187
186188
187189Adding new settings
@@ -265,7 +267,7 @@ the variables in one place, which could allow some components to overwrite other
265267In ``component.hxx `` there is the function ``get ``, which once called sets the
266268"final" and "final-domain" attributes:
267269
268- .. code-bloc :: ini
270+ .. code-block :: ini
269271
270272 T get(const Options& option, const std::string& location = " " ) {
271273 # if CHECKLEVEL >= 1
@@ -328,6 +330,9 @@ And there is a corresponding ``setBoundary`` that can be used for BC operations:
328330 return option;
329331 }
330332
333+ All of these functions are overloaded to accept both `Options ` and
334+ `GuardedOptions ` objects.
335+
331336These functions take a second argument which tells you where they were set, which is easier for debugging.
332337They are wrapped into additional functions, ``GET_VALUE `` and ``GET_NOBOUNDARY `` which automatically
333338include this argument.
@@ -516,21 +521,28 @@ Notes:
516521- The species name convention is that the charge state is last, after the `+ ` or `- `
517522 sign: `n2+ ` is a singly charged nitrogen molecule, while `n+2 ` is a +2 charged
518523 nitrogen atom.
524+
519525
520526Components
521527~~~~~~~~~~~~~~
522528
523529The basic building block of all Hermes-3 models is the
524530`Component `. This defines an interface to a class which takes a state
525- (a tree of dictionaries/maps), and transforms (modifies) it. After
526- all components have modified the state in turn, all components may
527- then implement a `finally ` method to take the final state but not
531+ (a tree of dictionaries/maps) and transforms (modifies) it. This is
532+ done by calling the public `Component::transform ` method. This will
533+ call the private `Component::transform_impl ` method, which must be
534+ overriden for each Component implementation.
535+
536+ After all components have modified the state in turn, all components
537+ may then implement a `finally ` method to take the final state but not
528538modify it. This allows two components to depend on each other, but
529539makes debugging and testing easier by limiting the places where the
530540state can be modified.
531541
532542.. doxygenstruct :: Component
533543 :members:
544+ :protected-members:
545+ :private-members:
534546
535547Components are usually defined in separate files; sometimes multiple
536548components in one file if they are small and related to each other (e.g.
@@ -552,7 +564,7 @@ file using a code like::
552564where `MyComponent ` is the component class, and "mycomponent" is the
553565name that can be used in the BOUT.inp settings file to create a
554566component of this type. Note that the name can be any string except it
555- can't contain commas or brackets () , and shouldn't start or end with
567+ can't contain commas or brackets, and shouldn't start or end with
556568whitespace.
557569
558570Inputs to the component constructors are:
@@ -565,12 +577,40 @@ The `name` is a string labelling the instance. The `alloptions` tree contains at
565577
566578* `alloptions[name] ` options for this instance
567579* `alloptions['units'] `
568-
580+
581+
582+ Component Permissions
583+ `````````````````````
584+
585+ All component constructors must pass a `Permissions ` object (see
586+ below) to the constructor on the `Component::Component ` base
587+ class. This specifies which variables will be read/written by the
588+ `Component::transform ` method and will be used to construct a
589+ `GuardedOptions ` object to be passed into
590+ `Component::transform_impl `. The `Permissions ` object can be further
591+ updated in the body of the constructor of your component using the
592+ `Component::setPermissions ` and `Component::substitutePermissions `
593+ methods. You should give read and write permissions to the minimum
594+ number of variables necessary, to avoid circular dependencies arising
595+ among components.
596+
597+ A number of substitutions will automatically be performed on your
598+ permissions (see `Permission Substitution `_), so that you can specify
599+ permissions for some variables for each species. For example, the
600+ following permissions would give read access to pressure for all
601+ species and density of ions::
602+
603+ MyComponent::MyComponent(const std::string &name, Options &options,
604+ Solver *solver) : Component({readOnly("species:{all_species}:pressure"),
605+ readOnly("species:{ions}:density")}) {}
606+
607+ See the documentation for `Component::declareAllSpecies ` for a list of
608+ all substitutions that will be performed.
609+
569610
570611Component scheduler
571612~~~~~~~~~~~~~~
572613
573-
574614The simulation model is created in `Hermes::init ` by a call to the `ComponentScheduler `::
575615
576616 scheduler = ComponentScheduler::create(options, Options::root(), solver);
@@ -599,7 +639,7 @@ scheduler looks up the options under the section of that name.
599639
600640 This would create two `Component ` objects, of type `component1 ` and
601641`component2 `. Each time `Hermes::rhs ` is run, the `transform `
602- functions of `component1 ` amd then `component2 ` will be called,
642+ functions of `component1 ` and then `component2 ` will be called,
603643followed by their `finally ` functions.
604644
605645It is often useful to group components together, for example to
@@ -629,6 +669,202 @@ in `group1`, and then `component3`.
629669 :members:
630670
631671
672+ Permissions
673+ ~~~~~~~~~~~~~~
674+
675+ The ``Permissions `` class can be used to store information about which
676+ variables within an ``Options `` object are allowed to be accessed and
677+ for what purpose. This is used to control the variables used by a
678+ ``Component ``. There is a hierarchy of four types of increasing
679+ permission. These are expressed using the
680+ `PermissionTypes ` `enum <https://en.wikipedia.org/wiki/Enumerated_type >`__:
681+
682+ #. **ReadIfSet: ** Only allowed to read variable if it is already set.
683+ #. **Read: ** Can read the contents of the variable. Assumes it has already been set.
684+ #. **Write: ** Can write variable. Makes no assumption about whether it has already been written or will be written again in future.
685+ #. **Final: ** This will be the last component to write to the variable. Only one component may have ``Final `` permission for a given variable.
686+
687+ The order these per permissions are listed in is significant: each
688+ higher permission implies a component also has all lower permissions. E.g.,
689+ write permission implies read permission as well.
690+
691+ Declaring Permissions for Particular Variables
692+ ``````````````````````````````````````````````
693+
694+ Permission information for a variable is stored in a
695+ `Permissions::VarRights ` object. The overwhelming majority of the
696+ permissions you would want to create can be constructed using one of
697+ the provided convenience-functions. For example::
698+
699+ Permissions::VarRights read_e_pressure = readOnly("species:e:pressure");
700+ Permissions::VarRights write_d_density = readWrite("species:d:density");
701+ Permissions::VarRights read_e_velocity_in_interior_if_set =
702+ readIfSet("species:e:velocity", Regions::Interior);
703+
704+ Permissions can be set to apply only to a particular region of the
705+ domain (e.g., the boundary or the interior) using a `Regions ` enum
706+ (see `Specifying a Region `_).
707+
708+ Creating Permissions Objects
709+ ````````````````````````````
710+
711+ Permission data like that created in the previous example can be used
712+ to construct a ``Permissions `` object. These objects describe the
713+ permissions for multiple variables.::
714+
715+ Permissions p({readOnly("time"),
716+ readOnly("species:e:pressure"),
717+ readWrite("species:e:momentum", Regions::Interior)});
718+
719+ A permission applied to a section of an ``Options `` object will apply
720+ to all variables contained within that section, unless a more specific
721+ permission is also set. Therefore, if we have a state with variables
722+ ``species:e:pressure ``, ``species:e:density ``, ``species:e:velocity ``,
723+ and ``species:e:momentum ``, then the following are equivalent::
724+
725+ Permissions p({readOnly("species:e"),
726+ readWrite("species:e:momentum")});
727+ Permissions p({readOnly("species:e:pressure"),
728+ readOnly("species:e:density"),
729+ readOnly("species:e:velocity"),
730+ readWrite("species:e:momentum")});
731+
732+ Specifying a Region
733+ ```````````````````
734+
735+ The `PermissionTypes ` are applied to particular regions of the domain.
736+ This allows, e.g., for there to be read permissions for the interior
737+ of the domain but write permissions for the boundaries. Regions are
738+ expressed using the `Permissions::Regions ` enum, which functions as a `bitset
739+ <https://en.wikipedia.org/wiki/Bit_array> `__. You can combine regions
740+ using bitwise logical operators.
741+
742+ .. doxygengroup :: RegionsGroup
743+ :members:
744+
745+ Permission Substitution
746+ ```````````````````````
747+
748+ Variable names can include labels, marked in curly-braces, that will
749+ later be substituted (using `Permissions::substitute ` and
750+ `Component::substitutePermissions `). Substitutions are necessary
751+ because, when declaring permissions for a `Component `, you may need to
752+ express that it can access some variable for all species (or all ions,
753+ all neutrals, etc.), but you won't yet know the names of all the
754+ species. For example, if you need to read the density of all species
755+ and write the collision frequency of all ions then you would write::
756+
757+ Permissions p({readOnly("species:{all_spcies}:density"),
758+ readWrite("species:{ions}:collision_frequency"});
759+
760+ If there are species e, d, d+, h, and h+ then the above will be
761+ equivalent to::
762+
763+ Permissions p({readOnly("species:e:density"),
764+ readOnly("species:d:density"),
765+ readOnly("species:d+:density"),
766+ readOnly("species:h:density"),
767+ readOnly("species:h+:density"),
768+ readWrite("species:d+:collision_frequency"),
769+ readWrite("species:h+:collision_frequency")});
770+
771+ These substitutions will be performed in
772+ `Component::declareAllSpecies `. See the documentation for that method
773+ for a full list of the substitutions which it can perform.
774+
775+ It can also be useful to define your own substitutions, to save
776+ repetitive declarations. For example, you could declare read
777+ permissions for electron density, pressure, temperature, velocity, and
778+ momentum as follows::
779+
780+ Permissions p({readOnly("species:e:{inputs}");
781+ p.substitute({"density", "pressure", "temperature", "velocity", "momentum"});
782+
783+ This is equivalent to having written::
784+
785+ Permissions p({readOnly("species:e:density"),
786+ readOnly("species:e:pressure")},
787+ readOnly("species:e:temperature")},
788+ readOnly("species:e:velocity")},
789+ readOnly("species:e:momentum")});
790+
791+ Permission Factory Functions
792+ ````````````````````````````
793+
794+ .. doxygengroup :: PermissionFactories
795+ :members:
796+
797+ Permissions Class
798+ `````````````````
799+ .. doxygenclass :: Permissions
800+ :members:
801+
802+ Further Implementation Details
803+ ``````````````````````````````
804+
805+ The above information should be sufficient for users that are
806+ developing or modifying components. The following explains in more
807+ detail how permission data is stored and should be read by anyone
808+ looking to modify the `Permissions ` or `GuardedOptions ` classes.
809+
810+ Permission information for a variable gets stored in
811+ `Permissions::AccessRights ` objects, which are arrays of
812+ `Regions `. Each element of the array corresponds to information about
813+ a permission level: ``{read_if_set, read, write, final} ``. To access
814+ the element for a desired permission level, you can index the array
815+ with the corresponding member of the `PermissionTypes ` enum::
816+
817+ Permissions::AccessRights rights;
818+ Regions read_regions = rights[PermissionTypes::Read];
819+ Regions write_regions = rights[PermissionTypes::Write];
820+
821+ The contents of each element of an `Permissions::AccessRights ` array
822+ is the set of regions for which the permissions apply. For example::
823+
824+ Permissions::AccessRights read_boundaries_if_set =
825+ {Regions::Boundaries, Regions::Nowhere, Regions::Nowhere,
826+ Regions::Nowhere};
827+ Permissions::AccessRights read_interior_write_boundaries =
828+ {Regions::Nowhere, Regions::Interior, Regions::Boundaries,
829+ Regions::Nowhere};
830+ Permissions::AccessRights final_write_all_regions =
831+ {Regions::Nowhere, Regions::Nowhere, Regions::Nowhere, Regions::All};
832+
833+ The `Permissions::VarRights ` struct is used to pair a variable name
834+ with a `Permissions::AccessRights ` array containing the permission
835+ information for that variable.
836+
837+
838+ GuardedOptions
839+ ~~~~~~~~~~~~~~
840+
841+ ``GuardedOptions `` objects combine a `Permissions ` object and an
842+ `Options ` object. They can be indexed just like normal ``Options ``
843+ objects but will return another ``GuardedOptions ``, wrapping the
844+ result. In order to read or write the contents of a ``GuardedOptions ``
845+ object you must use the ``get() `` or ``getWritable() `` methods,
846+ respectively. These will return the underlying (const) ``Options ``
847+ object, if you have the necessary permissions to access it. Otherwise,
848+ they will raise an exception.
849+
850+ If ``CHECKLEVEL `` is 1 or above, then the ``GuardedOptions `` will track
851+ which variables have actually been accessed. Lists of
852+ unread/unwritten variables can be returned with the ``unreadItems() ``
853+ and ``unwrittenItems() `` methods. If ``CHECKLEVEL `` is zero then
854+ calling these methods will raise an exception.
855+
856+ .. doxygenclass :: GuardedOptions
857+ :members:
858+
859+ .. note ::
860+ When indexing a ``GuardedOptions `` object, it will create a new
861+ ``GuardedOptions `` on-demand. This is unlike with a normal
862+ ``Options `` object which returns a reference to a preexisting child
863+ ``Options `` object. You generally should not store
864+ ``GuardedOptions `` by reference. You may be able to pass them by
865+ reference, but this requires you to think carefully about whether
866+ the argument is going to be an r-value or an l-value.
867+
632868.. _sec-tests :
633869
634870Tests
@@ -893,4 +1129,4 @@ There are two simple integrated tests to make sure that the collision frequency
8931129across `neutral_mixed `, `evolve_pressure `, `ion_viscosity ` and `neutral_parallel_diffusion `.
8941130A minimal 3D geometry is run for one RHS evaluation, and the test checks the log file
8951131to make sure the correct collisionalities were selected. One of the tests is for the `multispecies `
896- mode across all components, while the other is for `braginskii ` for plasma and `afn ` for neutrals.
1132+ mode across all components, while the other is for `braginskii ` for plasma and `afn ` for neutrals.
0 commit comments