The formula for series equivalence of resistors:
series equivalence = sum(resistors)
For parallel it is 1/(sum(1/resistors[i]))
I wrote code to return a list of resistors that is closest to a specified target value from a list, within a specified tolerance.
percentage_difference = lambda xi,xf: 100*(xf-xi)/xi def series_equivalance(R,target,tolerance): """ R = list of resistors present target = target value tolerance = range += % of target that is acceptable This function returns a list of resistors """ tol = tolerance/100 #converting tolerance to decimal if target < min(R): return "Your target is too small for series equivalence, Try parallel equivalence" else: r = R #dummy/copy R toriginal = target #dummy values for arguments made to not change arguments approximate = 0 #this is for exit condition, target in and of itself could be used but that would make algo unstable resistors_list = [] #list to return at the end while True: #Infinite loop because multiple exit conditions if (approximate >= (1-tol)*target and approximate <= (1+tol)*target) :#exit condition break if len(R) == 0: #If all values are used up return "All values used up, list: {}, approximate: {}".format(resistors_list,series_sum(resistors_list)) difference_from_target = [abs(toriginal-i) for i in R] #finding absolute difference of target from list of R values for i,v in enumerate(difference_from_target): if v == min(difference_from_target): #adding lowest differences to list approximate += r[i] #increment approximate by value from resistors with least difference toriginal -= r[i] #remove that from target dummy target resistors_list.append(r[i]) #adding to list to be returned r.remove(r[i]) break return "Resistors to use are {}, Approximated value: {}, %Δ of {}%".format(resistors_list,sum(resistors_list),percentage_difference(target,int(sum(resistors_list))))
So for example series_equivalance([1,2,3,4,5],7,0)
will return [5,2]
.
I want to a function that can do the same for parallel equivalence. How would I go about it?
Advertisement
Answer
Edit: I made a blog post which expands on the mip solution and can solve for minimum resistors satisfying a tolerance.
I’ve solved this two ways
- Using your function and feeding it inverse values
- Using a mixed integer linear program
Using your function and feeding it inverse values
This is a half way solution that just feeds in 1/R for the resistor values and gives it a target resistance of 1/target. Then, take reciprocal of the result and you have your resistor values. Doesn’t work with the tolerance value properly. Need to comment out the “Your target is too small” check for it to work.
def parallel_equivalance(R, target, tolerance): R_recip = [1/x for x in R] target_recip = 1/target tolerance_recip = tolerance # TODO: have a think about how to handle this. result_recip = series_equivalance(R_recip, target_recip, tolerance_recip) # resistors_to_use = [1/x for x in result_recip]
print(parallel_equivalance([1, 2, 3, 4, 5, 6, 7], 1.5555, 1e-2))
gives Resistors to use are [5, 2], Approximated value: 7, %Δ of 0.0%
Using a mixed integer linear program
For this method I use a mixed integer linear program to pick out which resistors to use, such that (1/r1 + 1/r2 + …) is as close as possible to 1/target. Solves very quickly (<1s) even when given ten thousand resistors to choose from.
You could modify this to pick the least number of resistors
(minimise sum(R_in_use)
) with the constraint that the
error value must be within some margin (say, error >= -eps
and error <= +eps
)
You’ll need to pip install mip
import mip def parallel_equivalance(R, target): R_recip = [1/x for x in R] target_recip = 1/target m = mip.Model() # Create new mixed integer/linear model. # Will take value of 1 when corresponding resistor is in use, otherwise 0. R_in_use = [m.add_var(var_type=mip.BINARY) for _ in R_recip] opt_r = sum([b * r for b, r in zip(R_in_use, R_recip)]) # This will be the optimal resistance error = opt_r - target_recip # Want to minimise the absolute value of this error. # create a variable which is greater than than the absolute value of the error. # Because we will be minimizing, this will be forced down to equal the # absolute value. Common trick, google "linear programming absolute value". abs_eror = m.add_var(lb=0) m += abs_eror >= error m += abs_eror >= -1 * error # Objective of the optimisation is to minimise the absolute error. m.objective = mip.minimize(abs_eror) m.verbose = False # Turn off verbose logging output. sol_status = m.optimize() print(sol_status) # This should be `optimal`. # Get the solution values telling us which resistors are in use. R_in_use_sol = [float(v) for v in R_in_use] # Pick out the values of the resistors corresponding to the resistors # that the optimiser decided to use. R_to_use = [r for r, i in zip(R, R_in_use_sol) if i > 0] solved_resistance = 1/sum(1/x for x in R_to_use) solved_error = 100 * (solved_resistance - target) / target print(f'Resistors {R_to_use} in parallel will produce ' f'R={solved_resistance:.3f}. ' f'Aiming for R={target:.3f}, ' f'error of {solved_error:.2f}%') return R_to_use def main(): print(f'mip version {mip.version}') sol = parallel_equivalance([1, 2, 3, 4, 5, 6, 7], 1.5555) sol = parallel_equivalance([1, 2, 3, 4, 5, 6, 7], 1.9) sol = parallel_equivalance(list(range(1, 100)), 123) sol = parallel_equivalance(list(range(1, 1000)), 5.954520294) sol = parallel_equivalance(list(range(1, 10_000)), 5.954520294) if __name__ == '__main__': main()
mip version 1.13.0 OptimizationStatus.OPTIMAL Resistors [2, 7] in parallel will produce R=1.556. Aiming for R=1.556, error of 0.00% OptimizationStatus.OPTIMAL Resistors [3, 5] in parallel will produce R=1.875. Aiming for R=1.900, error of -1.32% OptimizationStatus.OPTIMAL Resistors [99] in parallel will produce R=99.000. Aiming for R=123.000, error of -19.51% OptimizationStatus.OPTIMAL Resistors [27, 40, 41, 68, 69, 83, 123, 166, 172, 219, 277, 384, 391, 435, 453, 782, 837] in parallel will produce R=5.954. Aiming for R=5.955, error of -0.01% OptimizationStatus.OPTIMAL Resistors [7, 2001, 2021, 2065, 2130, 2152, 2160, 2176, 2191, 2202, 2216, 2245, 2270, 2279, 2282, 2283, 2313, 2342, 2351, 2381, 2414, 2417, 2497, 2728, 2789, 3449, 3514, 3566, 3575, 3621, 3701, 3789, 3812, 3868, 3879, 3882, 3903, 3936, 3952, 3959, 4128, 4145, 4152, 4158, 4183, 4373, 4382, 4430, 4441, 4498, 4525, 4678, 4722, 4887, 4953, 5138, 5178, 5253, 5345, 5358, 5543, 5593, 5620, 5774, 6002, 6247, 6364, 6580, 6715, 6740, 6819, 6904, 7187, 7293, 7380, 7468, 7533, 7782, 7809, 7846, 7895, 7914, 8018, 8067, 8242, 8309, 8414, 8507, 8515, 8590, 8627, 8872, 8893, 8910, 8952, 9171, 9282, 9311, 9376, 9477, 9550, 9657, 9736, 9792, 9822, 9876, 9982, 9988] in parallel will produce R=5.957. Aiming for R=5.955, error of 0.04%