I am trying to write a search for clock frequencies and divisors to generate a target frequency. However one constraint is the divisors need to be even (due to hardware limitations) and I can't find a way to model this.
There is no modulo operator support I get
"TypeError: unsupported operand type(s) for %: 'Variable' and 'int'"
and the following hack attempt using divide and multiply didn't work:
wantipp = cp.Parameter(name = 'wantedipp') # Desired IPP
div = cp.Variable(integer = True, name = 'div') # Divisor must be integral
ipp = cp.Variable(pos = True, name = 'ipp') # nsec
constraints = [
ipp == 1e9 / 6e6 * div, # Constrain IPP to divisor
div >= 2, div <= 65536, # Divisor must be 2-65536
div / 2 * 2 == div, # Divisor must be even (doesn't actually work)
]
objective = cp.Minimize(cp.abs(ipp - wantipp)) # Find closest possible IPP
prob = cp.Problem(objective, constraints);
for i in (1e3, 2e3, 1e6, 2e6, 123123, 5412341, 1233, 12541):
wantipp.value = i
prob.solve()
print('IPP %.3f nsec (%.3f Hz) -> Divisor %d %.3f nsec (%.3f Hz)' % (
i, 1e9 / i, div.value, ipp.value, 1e9 / ipp.value
))
IPP 1000.000 nsec (1000000.000 Hz) -> Divisor 6 1000.000 nsec (1000000.000 Hz)
IPP 2000.000 nsec (500000.000 Hz) -> Divisor 12 2000.000 nsec (500000.000 Hz)
IPP 1000000.000 nsec (1000.000 Hz) -> Divisor 6000 1000000.000 nsec (1000.000 Hz)
IPP 2000000.000 nsec (500.000 Hz) -> Divisor 12000 2000000.000 nsec (500.000 Hz)
IPP 123123.000 nsec (8121.959 Hz) -> Divisor 739 123166.667 nsec (8119.080 Hz)
IPP 5412341.000 nsec (184.763 Hz) -> Divisor 32474 5412333.333 nsec (184.763 Hz)
IPP 1233.000 nsec (811030.008 Hz) -> Divisor 7 1166.667 nsec (857142.857 Hz)
IPP 12541.000 nsec (79738.458 Hz) -> Divisor 75 12500.000 nsec (80000.000 Hz)
i.e. it ended up with a divisor of 739 etc.
(Note that I am starting with a fixed clock, later it will change)
I'm using CVXPY 1.0.25
, Python 3.7.5
on MacOSX 10.14.6
.
To answer the question in your tile, no there is no way to create such a constraint natively. If you could impose such a constraint directly, you would no longer have a convex hull as your solution space.
The fundamental issue here is that you can't create a constraint that makes the sampling of your range non-consecutive. This manifests itself in the interface in two ways that you have seen: you also can't have a constraint on a variable that depends on an expression containing that variable, and the operators //
and %
aren't defined for expressions.
The workaround for your particular case is to create a 1-to-1 mapping between the variable you solve for and the value you want to get from it. Even numbers are just integers multiplied by 2, so you can remove the explicit evenness constraint and do
constraints = [
ipp == 1e9 / 3e6 * div, # Constrain IPP to divisor
div >= 1, div <= 32768, # Divisor must be 2-65536
]
objective = cp.Minimize(cp.abs(ipp - wantipp)) # Find closest possible IPP
When you print the solution, show twice the solved value:
print('IPP %.3f nsec (%.3f Hz) -> Divisor %d %.3f nsec (%.3f Hz)' % (
i, 1e9 / i, 2 * div.value, ipp.value, 1e9 / ipp.value
))
Better yet, use a more modern f-string in place of the old-style interpolation:
print(f'IPP {i:.3f} nsec ({1e9 / i:.3f} Hz) -> Divisor {2 * div.value} {ipp.value:.3f} nsec ({1e9 / ipp.value:.3f} Hz)')