User-defined Operand: Modulo operator

  • 8 March 2024
  • 4 replies
  • 67 views

Userlevel 7
Badge +2

Hi all,

 

I’m not going to create a repository for this, but if you quickly need a modulo operator in your Merit Function, there you have it. Hx and Hy are the operand numbers (integer) that will be used as the numerator and denominator respectively.

The relevant part of the code is self-explanatory (hopefully):

int numerator = (int)TheApplication.OperandArgument1;
int denominator = (int)TheApplication.OperandArgument2;

...

double numerator_value = TheSystem.MFE.GetOperandAt(numerator).Value;
double denominator_value = TheSystem.MFE.GetOperandAt(denominator).Value;

operandResults[0] = numerator_value % denominator_value;

To install, download the ZIP file, extract it, and move UDOC12.exe to your

Documents/Zemax/ZOS-API/Operands

folder. Then, in the MFE you can do:

The rest of the division of 5.00 by 2.00 is 1.00.

In case you are wondering why I made this. I needed to have a linear polarization without caring about the orientation. I noticed that using CODA with data=110 (phase difference), some linear polarization angles were not achievable somehow and if I tried known configurations that would produce a linear polarization at those unachievable angles, CODA with data=110 would report 2pi. So, to make my original Merit function work, I’m just making sure that CODA with data=110 modulo 2pi is always zero :)

Hope this helps. Take care,

 

David

 


4 replies

Userlevel 6
Badge +2

Hey David, 

Fantastic simple example of how you can use the ZOS-API to extend the functionality of OpticStudio!!!

Since I love pushing OpticStudio to the limits and I had some free time today (avoiding doing my taxes), I got to thinking would it be possible to create a modulo function in Merit Function Editor with pure OS operands.  Since the tangent function is essentially a modulo function between -pi/2 and +pi/2, could we extend other trig functions to reproduce a modulo function.  A few trig functions I needed to figure out:

  1. pi value
    • acos(0)
  2. modulo function itself
    • a mod b = atan(tan(a/b*pi - pi/2))+pi/2)*b/pi
    • this doesn’t work if a/b is an integer
  3. check if a/b is an integer
    • isInteger = sin(pi* a/b) (0 if integer, a value between -1 and 1 otherwise)
  4. account for numerical roll-off (for future use with a ceiling function)
    • 0 => cos(acos(x))
  5. ceiling function
    • ceil(x) => 1/pi * atan(cot(pi*x))+1/2+x
    • cot(0) gives an error so we need to make all values between 1 and 2, not 0 and 1
  6. MFE doesn’t actually have a cotangent function
    • cot => cos(pi*x)/sin(pi*x)
  7. After #3-#6, we finally have a binary value (0 or 1) of whether a/b is an integer...just multiply the modulo function in #2 by the value in #6

This is 100% not the preferred way of getting the modulus of 2 numbers but I was happy I could get it with just trig functions and pure MFE operands...thought you might find this interesting.

 

Userlevel 7
Badge +2

@MichaelH 

 

Thanks a lot, this is brilliant. I was searching for a way to perform the modulo operation with native operands, but I quickly gave up. I have a couple of questions to make sure I understand this though.

  • I don’t understand the -pi/2 in the tan of the modulo function in 2, I see that it works but I can’t grasp why.
  • I also don’t understand the ceiling function is there any chance you can give me more insights on how to arrive at this formula?
  • What is this numerical roll-off that you are talking about? Can you show me an example of something wrong that might happen because of it? Also, you wrote cos(acos(x)) but the operands 32 and 33 are the other way around (does it matter?).

After I understand your implementation fully, I can try to compare the speed of the modulo operation: user-defined vs 30 native operands. My guess is that your implementation is still faster (even though it is heavy on the readability of the Merit function).

In any case, this is marvelous, makes my day.

Take care

 

David

Userlevel 6
Badge +2

Hey David,

This was mainly an exercise for fun...I would not recommending using this for anything but joy.

The following posts are the 2 main algorithms I used:

modular arithmetic - How to define mod without using integer division or rounding - Mathematics Stack Exchange

trigonometry - Are these trigonometric expressions for the ceiling and floor functions correct? - Mathematics Stack Exchange

As for the numerical roll-off, if you look at Row 30, figuring out if a/b is an integer actually gives 3.67e-16...this should be 0 but is represented as a really small number.  If you change Op#1 from 33 to 30 for the SUMM on Row 36, you can see the modulo becomes 7 rather than 0.  To really get a 0 or 1 for “isInteger” we need to use cos(acos(x)) to make sure 3.67e-16 is actually 0. 

As for the quickest way to get the modulo in the MFE, I would actually use your approach but to use a ZPLM (executing on the same process/thread as OpticStudio) rather than a UDOC (~10ms overhead per cycle to start & connect to ZOS):

a = PVHX()
b = PVHY()

a = OPER(a, 10)
b = OPER(b, 10)

IF b == 0
RETURN 9e9
ELSE
RETURN a - INTE(a / b) * b
ENDIF

 

Userlevel 7
Badge +2

@MichaelH

 

Thanks for your answer. I will definitely use it for fun. Thanks for the comment about the UDOC overhead, I didn’t know about those 10ms.

Please keep that curiosity-driven mindset, and take care,

 

David

Reply