resistor_calc/lib.rs
1//! A resistor value optimiser for circuit design.
2//!
3//! When provided with a set of constraints and relations for a series of resistors R1, R2, ..., it
4//! can present sets of values from standard series in order of increasing inaccuracy.
5//!
6//! # Example
7//! Given the following resistor network:
8//!
9//! 
10//!
11//! Where VADJ must remain at 0.8v, as R2 varies from no to full resistance,
12//! VOUT varies from 6v to 12v
13//!
14//! We can then describe the problem via the following constraints, plus a few extra bounds to
15//! eliminate either very small, or very large values, both of which may cause current issues.
16//! ```rust no_run
17//! extern crate resistor_calc;
18//!
19//! use resistor_calc::*;
20//!
21//! fn main() {
22//! let rcalc = RCalc::new(vec![&E24, &E6, &E24]);
23//!
24//! println!("Number of combinations: {}", rcalc.combinations());
25//!
26//! let res = rcalc
27//! .calc(
28//! ROpBuilder::new()
29//! .bound("R1+R2+R3 <= 1e6")
30//! .bound("R1+R2+R3 >= 1e4")
31//! .bound("0.8 * (1 + R1/R3) ~ 6.0")
32//! .bound("0.8 * (1 + (R1+R2)/R3) ~ 12.0")
33//! .finish(),
34//! )
35//! .expect("Error: No values satisfy requirements");
36//!
37//! res.print_best();
38//! }
39//! ```
40//! Running this example produces the results:
41//! ```text
42//! Number of combinations: 1185408
43//! Match 1:
44//! Error: 0.000
45//! Values: R1: 13K, R2: 15K, R3: 2K
46//!
47//! Match 2:
48//! Error: 0.000
49//! Values: R1: 130K, R2: 150K, R3: 20K
50//!```
51
52extern crate itertools;
53#[macro_use]
54extern crate lazy_static;
55
56use itertools::Itertools;
57
58use std::fmt;
59
60#[cfg(feature = "expr_builder")]
61mod expr_builder;
62
63#[cfg(feature = "expr_builder")]
64pub use expr_builder::ROpBuilder;
65
66const POWERS: &[f64] = &[1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6];
67
68lazy_static! {
69 /// RSeries constant for the E3 standard series
70 pub static ref E3: RSeries = RSeries::new(&[1.0, 2.2, 4.7]);
71 /// RSeries constant for the E6 standard series
72 pub static ref E6: RSeries = RSeries::extend(&E3, &[1.5, 3.3, 6.8]);
73 /// RSeries constant for the E12 standard series
74 pub static ref E12: RSeries = RSeries::extend(&E6, &[1.2, 1.8, 2.7, 3.9, 5.6, 8.2]);
75 /// RSeries constant for the E24 standard series
76 pub static ref E24: RSeries = RSeries::extend(
77 &E12,
78 &[1.1, 1.3, 1.6, 2.0, 2.4, 3.0, 3.6, 4.3, 5.1, 6.2, 7.5, 9.1]
79 );
80}
81
82pub(crate) fn _test_calc() -> RRes {
83 let r = RCalc::e3(2);
84 r.calc(ROpBuilder::new()
85 .bound("R1 + R2 ~ 500")
86 .finish()
87 ).unwrap()
88}
89
90/// A series of resistor values, constants are provided for standard resistor array values.
91#[derive(Debug)]
92pub struct RSeries {
93 values: Box<[f64]>,
94}
95
96impl RSeries {
97 fn new(series: &[f64]) -> Self {
98 RSeries {
99 values: series
100 .iter()
101 .cartesian_product(POWERS.iter())
102 .map(|(val, pow)| val * pow)
103 .collect::<Vec<f64>>()
104 .into_boxed_slice(),
105 }
106 }
107
108 fn extend(base: &RSeries, add: &[f64]) -> Self {
109 RSeries {
110 values: base.iter()
111 .cloned()
112 .chain(
113 add.iter()
114 .cartesian_product(POWERS.iter())
115 .map(|(val, pow)| val * pow),
116 )
117 .collect::<Vec<f64>>()
118 .into_boxed_slice(),
119 }
120 }
121
122 fn iter(&self) -> impl Iterator<Item = &f64> + Clone {
123 self.values.iter()
124 }
125
126 fn len(&self) -> usize {
127 self.values.len()
128 }
129}
130
131fn _format_rval(r: f64, unit: &str) -> String {
132 let mut val = format!("{}", r);
133 if val.contains('.') {
134 val.replace(".", unit)
135 } else {
136 val.push_str(unit);
137 val
138 }
139}
140
141fn _print_r(r: &f64) -> String {
142 if *r < 1000.0 {
143 _format_rval(*r, "R")
144 } else if *r < 1_000_000.0 {
145 _format_rval(*r / 1000.0, "K")
146 } else {
147 _format_rval(*r / 1_000_000.0, "M")
148 }
149}
150
151fn _print_res(r: &(u64, RSet)) {
152 let &(r, ref v) = r;
153 println!("Error: {:.3}\nValues: {}", (r as f64) / 1e9, v);
154}
155
156/// A binding of values to the set of resistors in a calculation.
157#[derive(Debug)]
158pub struct RSet(Box<[f64]>);
159
160impl RSet {
161 /// Retrieves the value of R{idx}, starting from R1, R2, ..., Rn
162 /// # Examples
163 /// ```
164 /// # let ret = {
165 /// # use resistor_calc::{RCalc, ROpBuilder};
166 /// # let r = RCalc::e3(2);
167 /// # r.calc(ROpBuilder::new()
168 /// # .bound("R1 + R2 ~ 500")
169 /// # .finish()
170 /// # ).unwrap()
171 /// # };
172 /// for (err, rset) in ret.iter() {
173 /// println!("R1 = {}", rset.r(1));
174 /// println!("R2 = {}", rset.r(2));
175 /// }
176 /// ```
177 pub fn r(&self, idx: usize) -> f64 {
178 self.0[idx - 1]
179 }
180
181 /// Returns the sum of all the values in the set. Good for presenting overall bounds on dividers.
182 pub fn sum(&self) -> f64 {
183 self.0.iter().sum()
184 }
185}
186
187impl fmt::Display for RSet {
188 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
189 let sep = if f.alternate() { "\n" } else { ", " };
190 write!(
191 f,
192 "{}",
193 self.0
194 .iter()
195 .enumerate()
196 .map(|(i, r)| format!("R{}: {}", i + 1, _print_r(r)))
197 .join(sep)
198 )
199 }
200}
201
202/// Stores the result of a calculation.
203#[derive(Debug)]
204pub struct RRes {
205 res: Vec<(u64, RSet)>,
206}
207
208impl RRes {
209 /// Print all combinations that share the lowest error value.
210 pub fn print_best(&self) {
211 let best_err = self.res[0].0;
212 for (idx, res) in self.res
213 .iter()
214 .take_while(|(err, _)| *err == best_err)
215 .enumerate()
216 {
217 println!("Match {}:", idx + 1);
218 _print_res(res);
219 println!();
220 }
221 }
222
223 /// Provides an iterator over all results in the object. They are presented from lowest to
224 /// highest error value, within a given error value different combinations may be presented in
225 /// any order. The item type is `&(u64, RSet)`, where the first value is parts in a billion
226 /// error (`floor(err * 1e9)`).
227 pub fn iter(&self) -> impl Iterator<Item = &(u64, RSet)> {
228 self.res.iter()
229 }
230}
231
232/// Main calculator struct
233#[derive(Debug)]
234pub struct RCalc {
235 rs: Vec<&'static RSeries>,
236}
237
238impl RCalc {
239 /// Creates a new RCalc with the series used for the R values provided as a vec.
240 /// # Examples
241 /// To create a calculator that will vary over 4 resistors R1, R2, R3 and R4, where we want to
242 /// draw R1 and R2 from the E24 series, R3 from the E6 series and R4 from the E12 series would
243 /// be done as follows:
244 /// ```
245 /// # use resistor_calc::*;
246 /// let rcal = RCalc::new(vec![&E24, &E24, &E6, &E12]);
247 /// ```
248 pub fn new(rs: Vec<&'static RSeries>) -> Self {
249 RCalc { rs }
250 }
251
252 /// Creates a new RCalc with `count` resistors drawn from the E3 series.
253 pub fn e3(count: usize) -> Self {
254 Self::new(vec![&E3; count])
255 }
256
257 /// Creates a new RCalc with `count` resistors drawn from the E6 series.
258 pub fn e6(count: usize) -> Self {
259 Self::new(vec![&E6; count])
260 }
261
262 /// Creates a new RCalc with `count` resistors drawn from the E12 series.
263 pub fn e12(count: usize) -> Self {
264 Self::new(vec![&E12; count])
265 }
266
267 /// Creates a new RCalc with `count` resistors drawn from the E24 series.
268 pub fn e24(count: usize) -> Self {
269 Self::new(vec![&E24; count])
270 }
271
272 /// Returns the number of combinations of values that exist for the configured resistors and
273 /// series. This will fairly directly map to the amount of time taken to calculate value
274 /// combinations.
275 pub fn combinations(&self) -> u128 {
276 self.rs.iter().map(|r| r.len() as u128).product()
277 }
278
279 /// Given a testing function `f` thats maps from a set of input resistors to `Option<f64>` this
280 /// will calculate the results for the resistors and series configured and return the result as
281 /// an `RRes`. `f` should map combinations that are unsuitable to `None` and combinations that
282 /// are suitable to `Some(err)` where `err` is a `f64` describing how far from perfect the
283 /// combination is. `f` is often supplied with the use of the `ROpBuilder` struct.
284 pub fn calc(&self, f: impl Fn(&RSet) -> Option<f64>) -> Option<RRes> {
285 let mut res: Vec<(u64, RSet)> = self.rs
286 .iter()
287 .map(|r| r.iter().cloned())
288 .multi_cartesian_product()
289 .filter_map(|v| {
290 let rs = RSet(v.into_boxed_slice());
291 f(&rs).map(|err| ((err * 1e9).round() as u64, rs))
292 })
293 .collect();
294 res.sort_by_key(|(err, _rs)| *err);
295 if !res.is_empty() {
296 Some(RRes { res })
297 } else {
298 None
299 }
300 }
301}