Parameter Access Mechanisms
Throughout GR3.x there is an inconsistent and/or manual way of changing the parameters of a block. It is desired to have all of the possible access mechanisms consistent and consolidated, which include:
- Block Constructor
- Setters and Getters
- Tags
- Message Ports
- RPC
Each of these ways of changing a variable in a block must be done manually and is inconsistently handled across blocks in the library. Also, by consolidating the access, we can pipe changes through the scheduler when running, so there is no conflict between, say, the setters and the work() function - removing the need for mutexes. Let’s look at how we can bring all this together (as done in newsched currently).
First, we utilize the new PMT library to represent each “parameter” of a block as a PMT. The block class keeps these PMTs accessible by an id which is autogenerated as an enum (and mappable to/from string) in the top level myblock.h class. So we have a way to store a generic object (PMT) now that is accessible from many different places.
In block.h
, a class holding shared pointers to these parameter PMTs is defined:
parameter_config d_parameters;
struct parameter_config {
std::map<std::string, pmt_sptr> param_map;
std::map<int, pmt_sptr> param_map_int;
size_t num() { return param_map.size(); }
void add(const std::string& name, int id, pmt_sptr b)
{
param_map[name] = b;
param_map_int[id] = b;
}
pmt_sptr get(const std::string& name) { return param_map[name]; }
pmt_sptr get(int id) { return param_map_int[id]; }
void clear() { param_map.clear(); }
};
We will use the parameters from a multiply_const
block to show how parameters can be accessed. This block has 2 parameters defined in the yaml:
parameters:
- id: k
label: Constant
dtype: T
settable: true
- id: vlen
label: Vec. Length
dtype: size_t
default: 1
The above yaml gets autogenerated to make multiply_const.{h,cc}
which configures the parameters without any intervention from the user.
In multiply_const.h:
virtual void set_k(T k);
virtual T k();
enum params : uint32_t { id_k,id_vlen, num_params };
pmt_sptr param_k;
pmt_sptr param_vlen;
and in multiply_const.cc:
d_param_str_map = { {"k", id_k},{"vlen", id_vlen},};
d_str_param_map = { {id_k, "k"},{id_vlen, "vlen"},};
param_k = std::make_shared<pmtf::pmt>(args.k);
add_param("k", d_param_str_map["k"], param_k);
param_vlen = std::make_shared<pmtf::pmt>(args.vlen);
add_param("vlen", d_param_str_map["vlen"], param_vlen);
}
all this boilerplate for free – nice, right?
Let’s look more at how this fits into all the places where parameters have to be configured by the developer
Block Constructor
The first change we have made with the block constructor is to lump all the parameters that will be set into a struct (that is autogenerated in the top level header). The yaml above generates the following struct:
struct block_args {
T k;
size_t vlen = 1;
};
So the block constructor (and make
factory method) can just use this struct. This prevents changes to the yaml from forcing the developer to change the constructor in several places.
It is not necessary now in the constructor to set private variables for simple values as the PMTs of the base class hold the value and can be accessed from the work function.
The only thing to note, is that in the constructor, setting internal variables for things that get set from constructor args need to use “args.”: d_my_var = args.my_var
Setters and Getters
For each parameter that is “settable” (not default) and/or “gettable” (default), setter and getter methods are autogenerated at the base class. These trigger the block method “request_parameter_change” or “request_parameter_query” respectively - and if the scheduler is running, trigger a callback to get this in between work calls
The Setters and Getters (or the on_parameter_change callbacks) can be overridden in the block implementation to do things like updating NCOs and such.
But the following code is autogenerated in the base block class (e.g. for multiply_const, in multiply_const.cc)
// Settable Parameters
template <class T>
void multiply_const<T>::set_k(T k)
{
return request_parameter_change(params::id_k, k);
}
template <class T>
T multiply_const<T>::k()
{
return pmtf::get_as<T>(request_parameter_query(params::id_k));
}
Tags
Not currently implemented, but a common tag should trigger the update of parameters. Since they are PMTs, a tag that has the name of the parameter and it’s new value should be able to work
The goals with any mechanism to update parameters via tags should consider the following:
- Shouldn’t have to implement the parameter changes in the work function
- Defeats the purpose of common access mechanisms
- Updates should happen in the scheduler prior to work() being called
Message Ports
Each block by default (via autogenerated code) instantiates a message port named “param_update”, and when receiving a PMT, will update the associated parameter
RPC
Via python bindings, a general block_method can call the method on the specified block (setter or getter)