![]() |
![]() ![]()
Experimental Physics and
| ||||||||||||||
|
Hello Érico, I might have misunderstood what you are asking, so let me try to rephrase to confirm. You would like to use an aSub in such a way that the sizes of the input and/or output arrays are dynamic. Is that correct? If I misunderstood, sorry for the noise. But if that's what you are asking, then you are right in believing that things should be simpler than what you describe in your message. Things are simpler. The NOX and NOVX groups of fields are allocations sizes, much like NELM on a waveform. The NEX and NEVX are the current sizes, much like NORD on a waveform. Suppose you have an aSub with both INPA and OUTA pointing to waveforms. You need to
That's it. The record support code takes care of setting prec-nea based on the source waveform
for you, and using the prec->neva
you provide to set the destination waveform. Best, On 5/3/23 23:16, Érico Nogueira Rolim
via Tech-talk wrote:
Caution: This email originated from outside of Cosylab. Hi! I'm developing an IOC using asyn, and it controls an acquisition system, where the resulting data is exported in PVs (AAI records). The number of samples is controlled by another PV, and is propagated into the AAI record from the doCallbacks*Array() functions, and can be found in the value of the NORD field of the record. This is useful because clients like pyepics's PV.get(), camonitor, pvget and pvmonitor all read only NORD elements from the PV. I'd like to perform some calculations on top of the acquisition data (for now, things like applying a gain and offset on all elements), and it seems more adequate to do so in a database, using records, instead of adding parameters to my asyn driver so it can do the calculations itself and expose another array: future developments get easier, things are more flexible, and I have less code in my driver. The alternatives I took a look at were aSubRecord [1] and aCalcoutRecord [2]. Neither of them claim to do anything re. NORD of the input parameters; given that both of them support arbitrary computations with either of those, it makes sense that there couldn't be a default behavior for this functionality. But it definitely feels inefficient to run a loop for NELM (can be something like 1_000_000) elements in an array when we have only NORD (perhaps 1_000?) valid elements. Another IOC in my org uses aSubRecord to perform the calculation on NELM elements, and then uses subArrayRecord [3] to expose only NORD elements from the intermediary array, but it means adding a seemingly unnecessary intermediary record (and I don't know if there are memory usage implications for that). Using aSubRecord, I came up with the following solution: record(aSub, "$(S)$(RTM_CHAN)$(ACQ_NAME)CurrentConv"){ field(SNAM, "asub_goff") field(INPA, "$(S)$(RTM_CHAN)$(ACQ_NAME)CurrentRawData CP") field(NOA, "$(NELM)") field(FTA, "SHORT") field(INPB, "$(S)$(RTM_CHAN)$(ACQ_NAME)CurrentRawData.NORD") field(INPC, "1") field(INPD, "$(S)$(RTM_CHAN)CurrOffset-SP") field(INPE, "$(S)$(RTM_CHAN)CurrGain-SP") field(OUTA, "$(S)$(RTM_CHAN)$(ACQ_NAME)CurrentData PP") field(FTVA, "FLOAT") field(NOVA, "$(NELM)") } where asub_goff is shown at the end of this email. The highlight from it is that it copies the value from INPB into prec->neva, which, though it is documented as read-only in [1] (maybe that's only about DB/channel access?), does write into the NORD field of the record in OUTA. However, it feels a bit dirty to do things like this; supporting DB links in the NEA field, so we could pass CurrentRawData.NORD to it instead, and then use prec->nea in our subroutine, seems much better. I believe the same could go for fields like NOA and NOVA, where CurrentRawData.NELM and CurrentData.NELM, respectively, could be used (or it could even get that information from the INPA link? though I have no idea if that would be possible). On top of that, the FT* behavior is a bit confusing/inconsistent. It seems the record does convert things for scalar values, so, for example, FTD is DOUBLE by default (per the documentation) and the value read from it (using (epicsFloat64 *)) is correct, despite the CurrOffset-SP.VAL field being of type LONG. But for the array inputs, my code didn't work correctly until I specified FTA and FTVA, because the pointers in prec->a and prec->vala were always to the underlying arrays of epicsInt16 and epicsFloat32, respectively, despite prec->fta and prec->ftva specifying them as DOUBLE. Is this a matter of documentation, or would it be possible to improve the ergonomics (by automatically setting FT* fields?), or at least have some layer print a warning about the wrong types being used? Am I missing some other obvious way of implementing this? Are there better ways? Thanks in advance, Érico [1] https://epics.anl.gov/base/R7-0/7-docs/subArrayRecord.html [2] https://epics-modules.github.io/calc/aCalcoutRecord.html [3] https://epics.anl.gov/base/R7-0/7-docs/subArrayRecord.html asub_goff implementation file: template <class T> static inline void apply_fn(void *v, epicsInt16 ft, T fn) { if (ft == menuFtypeSHORT) fn((epicsInt16 *)v); else if (ft == menuFtypeLONG) fn((epicsInt32 *)v); else if (ft == menuFtypeINT64) fn((epicsInt64 *)v); else if (ft == menuFtypeFLOAT) fn((epicsFloat32 *)v); else if (ft == menuFtypeDOUBLE) fn((epicsFloat64 *)v); else throw std::runtime_error("unimplemented type"); } template <class T> static inline void get_scalar (T &out, void *v, epicsInt16 ft) { apply_fn(v, ft, [&out](auto rv){ out = *rv; }); } /** This array subroutine is used to apply gain and offset to an array, * given the following formula: * * out = (in * pre_gain + offset) * post_gain * * This function expects as input: * - array in A * - array size in B * - pre_gain in C * - offset in D * - post_gain in E * * And will write its output to: * - array in OUTA */ static long asub_goff(aSubRecord *prec) { /* sanity checking: these input elements were provided */ if (prec->neb != 1 || prec->nec != 1 || prec->ned != 1 || prec->nee != 1) return 1; /* sanity checking: the arrays are big enough */ printf("nova %d noa %d\n", prec->nova, prec->noa); if (prec->nova < prec->noa) return 2; epicsUInt32 elements; get_scalar(elements, prec->b, prec->ftb); double pre_gain, offset, post_gain; get_scalar(pre_gain, prec->c, prec->ftc); get_scalar(offset, prec->d, prec->ftd); get_scalar(post_gain, prec->e, prec->fte); /* write NORD field in output */ prec->neva = elements; auto apply_goff = [pre_gain, offset, post_gain, elements](auto out, auto in) { for (epicsUInt32 i = 0; i < elements; i++) out[i] = (in[i] * pre_gain + offset) * post_gain; }; /* run apply_goff regardless of the types of INPA and OUTA, * without having to convert anything */ apply_fn(prec->a, prec->fta, [&prec, apply_goff](auto in) { apply_fn(prec->vala, prec->ftva, [in, apply_goff](auto out) { apply_goff(out, in); }); }); return 0; } epicsRegisterFunction(asub_goff); Aviso Legal: Esta mensagem e seus anexos podem conter informações confidenciais e/ou de uso restrito. Observe atentamente seu conteúdo e considere eventual consulta ao remetente antes de copiá-la, divulgá-la ou distribuí-la. Se você recebeu esta mensagem por engano, por favor avise o remetente e apague-a imediatamente. Disclaimer: This email and its attachments may contain confidential and/or privileged information. Observe its content carefully and consider possible querying to the sender before copying, disclosing or distributing it. If you have received this email by mistake, please notify the sender and delete it immediately.
| ||||||||||||||
ANJ, 04 May 2023 |
![]() · Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing · |