1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 <2023> 2024 2025 | Index | 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 <2023> 2024 2025 |
<== Date ==> | <== Thread ==> |
---|
Subject: | Re: Standard/clean way to copy NORD from one waveform to another (and aSub limitations) |
From: | Érico Nogueira Rolim via Tech-talk <tech-talk at aps.anl.gov> |
To: | "tech-talk at aps.anl.gov" <tech-talk at aps.anl.gov>, Jure Varlec <jure.varlec at cosylab.com> |
Date: | Thu, 4 May 2023 11:56:48 +0000 |
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?
It is exactly 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
- In the database, set NOA to match NELM of the waveform at INPA.
- In the database, set NOVA to match NELM of the waveform at OUTA.
- In the subroutine, access only the first prec->nea elements of prec->a.
- In the subroutine, set prec->neva to the desired number of output elements, then write to the first prec->neva elements of prec->vala.
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.
Ah, I see where I got confused! Because prec->noa and prec->nova weren't set automatically, I assumed prec->nea wouldn't be either, and didn't check that assumption. I changed my code to use prec->nea and it is working just fine.
Thank you very much, Jure!
Cheers,
Érico
Best,
Jure
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.