{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "a8efe4e4-35a2-4bb6-899f-4804a759acf4", "metadata": {}, "outputs": [], "source": [ "from pulp import *\n", "import numpy as np\n", "import math\n", "import tqdm\n", "import itertools\n", "from matplotlib import pyplot as plt\n", "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": 2, "id": "b42474d5-df3f-49b0-bd28-c3bc2c2ffa94", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "40\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/tmp/ipykernel_91261/1305871445.py:31: TqdmDeprecationWarning: This function will be removed in tqdm==5.0.0\n", "Please use `tqdm.notebook.tqdm` instead of `tqdm.tqdm_notebook`\n", " for node_assignments in tqdm.tqdm_notebook(list(itertools.product(enumerate_combinations(), repeat=total_switches))):\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "5601fb69f644476c9828c66cd7c3e9fa", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/47458321 [00:00 \u001b[39m\u001b[32m32\u001b[39m node_values = [\u001b[38;5;28;43msum\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43massy\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mfor\u001b[39;00m assy \u001b[38;5;129;01min\u001b[39;00m node_assignments]\n\u001b[32m 33\u001b[39m result = objective(node_values)\n\u001b[32m 34\u001b[39m objective_values[result] = node_assignments\n", "\u001b[31mKeyboardInterrupt\u001b[39m: " ] } ], "source": [ "\n", "e_series_base = [1.0, 2.2, 4.7]\n", "decades = [1, 10]\n", "value_catalog = [base*mul for mul in decades for base in e_series_base]\n", "\n", "# maximum number of sum elements in a single node\n", "max_sum_n = 3\n", "# total number of switched positions\n", "total_switches = 4\n", "\n", "def objective(node_values):\n", " node_values = list(sorted(node_values))\n", " deltas = [ abs((b - a) / min(a, b) - 1.0) for a, b in zip(node_values, node_values[1:]) ]\n", " return max(deltas)\n", "\n", "def enumerate_combinations(n=max_sum_n):\n", " for i in range(1, n+1):\n", " yield from itertools.combinations_with_replacement(value_catalog, i)\n", "\n", "def reject_combinations(it, tol=0.05):\n", " it = list(sorted(it, key=len))\n", " out = {}\n", " for comb in it:\n", " value = sum(comb)\n", " if not any(abs(x/value - 1.0) < tol for x in out):\n", " out[value] = comb\n", " return list(out)\n", "\n", "print(len(list(reject_combinations(enumerate_combinations()))))\n", "\n", "objective_values = {}\n", "for node_assignments in tqdm.tqdm_notebook(list(itertools.product(enumerate_combinations(), repeat=total_switches))):\n", " node_values = [sum(assy) for assy in node_assignments]\n", " result = objective(node_values)\n", " objective_values[result] = node_assignments\n", "\n", "fig, ax = plt.subplots()\n", "ax.plot(sorted(objective_values.keys()))" ] }, { "cell_type": "code", "execution_count": null, "id": "d4c5b36f-2582-4547-8644-a9c32cbec630", "metadata": {}, "outputs": [], "source": [ "objective_values[min(objective_values.keys())]" ] }, { "cell_type": "code", "execution_count": null, "id": "67c388e7-4f64-462b-8309-ddd7d4bc04f1", "metadata": {}, "outputs": [], "source": [ "def objective(node_values, v_min = min(value_catalog), v_max = max(value_catalog) * 1.2, n_samples = 50):\n", " rms_sum = 0\n", " for target in np.logspace(v_min, v_max, n_samples):\n", " delta = math.inf\n", "\n", " for assignment in itertools.product([0, 1], repeat=total_switches):\n", " assignment_value = sum(v * x for v, x in zip(node_values, assignment))\n", " delta = min(delta, assignment_value)\n", " \n", " rms_sum += delta**2\n", " rms_sum /= n_samples\n", " return np.sqrt(rms_sum)" ] }, { "cell_type": "code", "execution_count": null, "id": "aff170ff-94bb-45d0-93e6-2814b61352ca", "metadata": {}, "outputs": [], "source": [ "\n", "e_series_base = [1.0, 2.2, 4.7]\n", "decades = [1, 10, 100, 1000]\n", "value_catalog = [base*mul for mul in decades for base in e_series_base]\n", "\n", "# maximum number of sum elements in a single node\n", "max_sum_n = 3\n", "# total number of switched positions\n", "total_switches = 10\n", "\n", "prob = LpProblem('Decade matrix optimization', LpMinimize)\n", "\n", "#constraints = []\n", "#node_exprs = []\n", "#for i in range(total_switches):\n", "# variables = {LpVariable(f'sw{i}_n_{v:.1f}', lowBound=0, upBound=3, cat='Integer'): v for v in value_catalog}\n", "# node_expression = LpAffineExpression(variables)\n", "# node_constraint = lpSum(list(variables.keys())) <= max_sum_n\n", "# constraints.append((node_constraint, f'cons_n{i}'))\n", "# node_exprs.append(node_expression)\n", "\n", "max_deviation = LpVariable('max_dev')\n", "probe_points = 50\n", "choice_vars = LpVariable.dicts('choice', (range(probe_points), range(total_switches)), cat='Binary')\n", "for i, target in enumerate(np.logspace(min(value_catalog), max(value_catalog)*1.2, probe_points)):\n", " choice_obj = lpSum([choice_vars[i][j] * node_values[j] for j in range(total_switches)])\n", " choice_dev = choice_obj - target\n", " prob += choice_dev <= max_deviation\n", "\n", "for cons in constraints:\n", " prob += cons\n", "\n", "prob.writeLP('decade.lp')\n", "prob.solve()\n", "\n", "LpStatus[prob.status]" ] }, { "cell_type": "code", "execution_count": null, "id": "f482f57b-e081-4ab1-abd5-dbaea040865f", "metadata": {}, "outputs": [], "source": [ "\n", "prob = LpProblem('Decade matrix optimization', LpMinimize)\n", "\n", "constraints = []\n", "node_exprs = []\n", "for i in range(total_switches)\n", " variables = {LpVariable(f'sw{i}_n_{v:.1f}', lowBound=0, upBound=3, cat='Integer'): v for v in values}\n", " node_expression = LpAffineExpression(variables)\n", " node_constraint = lpSum(list(variables.keys())) <= max_sum_n\n", " constraints.append((node_constraint, f'cons_n{i}'))\n", " node_exprs.append(node_expression)\n", "\n", "probe_points = 50\n", "choice_vars = LpVariable.dicts('Choice', (probe_points, total_switches), cat='Binary')\n", "choice_obj = lpSum(choice_vars[i][j] * node_exprs[j]\n", "\n", "for cons in constraints:\n", " prob += cons\n", "\n", "prob.writeLP('decade.lp')\n", "prob.solve()\n", "\n", "LpStatus[prob.status]" ] }, { "cell_type": "code", "execution_count": null, "id": "976e724b-9df4-4cc4-a4a4-dc9b027f240c", "metadata": {}, "outputs": [], "source": [ "\n", "e_series_base = [1.0]\n", "decades = [1, 10, 100, 1000, 10000]\n", "value_catalog = [base*mul for mul in decades for base in e_series_base]\n", "value_catalog = [0, *value_catalog, math.inf]\n", "\n", "# maximum number of elements in a single node\n", "max_elements = 3\n", "\n", "topologies = ['ss', 'sp', 'pp']\n", "found_values = []\n", "\n", "def series(a, b):\n", " if math.isclose(a, 0) or math.isclose(b, 0):\n", " return 0\n", " if not math.isfinite(a):\n", " return b\n", " if not math.isfinite(b):\n", " return a\n", " return 1 / (1/a + 1/b)\n", "\n", "def parallel(a, b):\n", " return a + b\n", "\n", "for assignment in itertools.combinations_with_replacement(value_catalog, max_elements):\n", " a, b, c = assignment\n", " for topo in topologies:\n", " if topo[0] == 's':\n", " im = series(a, b)\n", " else:\n", " if not math.isfinite(a) or not math.isfinite(b):\n", " continue\n", " im = parallel(a, b)\n", "\n", " if topo[1] == 's':\n", " out = series(im, c)\n", " else:\n", " if not math.isfinite(c):\n", " continue\n", " out = parallel(im, c)\n", " \n", " found_values.append((out, topo, assignment))\n", "\n", "for i in range(20):\n", " target = 2**i\n", " results = list(sorted(found_values, key=lambda x: abs(target - x[0])))\n", " print(f'=== Target: {target:.1f} ===')\n", " for out, topo, assignment in results[:3]:\n", " a, b, c = assignment\n", " print(f'{out:>6.1f} (+/- {abs(target/out - 1.0)*100:>6.2f} %) {topo} {a:>6.1f} {b:>6.1f} {c:>6.1f}')\n", " print()\n", "\n", "found_values = list(sorted(found_values, key=lambda x: x[0]))\n", "fig, ax = plt.subplots()\n", "ax.scatter([x[0] for x in found_values], [x[0] for x in found_values])\n", "ax.set_xscale('log')\n", "ax.set_yscale('log')" ] }, { "cell_type": "code", "execution_count": null, "id": "ffa56679-99cf-4310-80fb-3f42394436bf", "metadata": {}, "outputs": [], "source": [ "vals = [0.5 * 2**i for i in range(9)]\n", "foo = list(sorted([1/(1/10 + sum((1/vals[i]) * int(bool(j & (1<= 50 V\n", "\n", "### SMD C0G MLCC\n", "* Yageo CC0402FRNPO9BN101 (C327198) 100 pF 1% C0G 0402 @ 0.01 USD\n", "* Yageo CC0603FRNPO9BN102 (C309468) 1 nF 1% C0G 0603 @ 0.01 USD\n", "* Yageo CC0603JRNPO9BN103 (C389113) 10 nF 5% C0G 0603 @ 0.01 USD\n", "* Murata GRM31C5C1H104JA01L (C97946) 100 nF 5% C0G 1206 @ 0.10 USD\n", "\n", "## THT MPP\n", "* Kyet MPP155J2S1001 (C42396643) 1.5 µF 5% MPP P10mm @ 0.26 USD\n", "* DGCX MPP305KE2A130B200ZR (C46596153) 3 µF 10% MPP P20mm @ 0.10 USD\n", "* Xiamen Faratronic C252A565J60C000 (C401286) 5.5 µF 5% MPF P15mm @ 0.70 USD\n", "\n", "## THT alternative (WIMA/Reichelt)\n", "* (MKS2-63 1,0µ) MKS2 PET-Kondensator, 1,0 µF, 5 %, 63 VDC, RM 5 @ 0.50 EUR\n", "* (MKS2-63 1,5µ) MKS2 PET-Kondensator, 1,5 µF, 5 %, 63 VDC, RM 5 @ 0.70 EUR\n", "* (MKS4-63 4,7µ) MKS4 PET-Kondensator, 4,7 µF, 10 %, 63 VDC, RM 15@ 1.00 EUR\n", "\n", "| Value | Range A | Range B | Range C |\n", "|--------|---------|---------|---------|\n", "| 0.1 | a1 | | |\n", "| 0.2 | a2 | | |\n", "| 0.4 | a3 | | |\n", "| 0.8 | a4 | | |\n", "| 1.6 | a5 | b1 | |\n", "| 3.2 | a6 | b2 | |\n", "| 6.4 | a7 | b3 | |\n", "| 12.8 | a8 | b4 | |\n", "| 25.6 | a9 | b5 | c1 |\n", "| 51.2 | | b6 | c2 |\n", "| 102.4 | | b7 | c3 |\n", "| 204.8 | | b8 | c4 |\n", "| 409.6 | | b9 | c5 |\n", "| 819.2 | | | c6 |\n", "| 1638.4 | | | c7 |\n", "| 3276.8 | | | c8 |\n", "| 6553.6 | | | c9 |" ] }, { "cell_type": "markdown", "id": "32aaf9a4-3cf0-4b98-bfc5-e9ef72647521", "metadata": {}, "source": [ "# Relay Assignment\n", "\n", "| Relay | Range A | Range B | Range C |\n", "|-------|---------|---------|-----------|\n", "| K1 | 0.1 | 1.6 | 819.2 |\n", "| K2 | 0.2 | 3.2 | 1638.4 |\n", "| K3 | 0.4 | 6.4 | 3276.8 |\n", "| K4 | 0.8 | 12.8 | 6553.6 |\n", "| K5 | 1.6 | 25.6 | 25.7 (J) |\n", "| K6 | 3.2 | 51.2 | 51.2 (J) |\n", "| K7 | 6.4 | 102.4 | 102.4 (J) |\n", "| K8 | 12.8 | 204.8 | 204.8 (J) |\n", "| K9 | 25.6 | 409.6 | 409.6 (J) |" ] }, { "cell_type": "code", "execution_count": 3, "id": "92fe0a32-df3d-4aea-99d5-fcf207f244b3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Max capacitance for target capacitance 1.6 µF at 5% tol: 1.560 µF, at 10 % tol: 1.489 µF.\n", "Max capacitance for target capacitance 3.3 µF at 5% tol: 3.121 µF, at 10 % tol: 2.979 µF.\n", "Max capacitance for target capacitance 6.6 µF at 5% tol: 6.242 µF, at 10 % tol: 5.958 µF.\n" ] } ], "source": [ "for C in [1638.4, 3276.8, 6553.6]:\n", " print(f'Max capacitance for target capacitance {C/1000:.1f} µF at 5% tol: {C/1.05/1000:.3f} µF, at 10 % tol: {C/1.10/1000:.3f} µF.')" ] }, { "cell_type": "code", "execution_count": 52, "id": "ddb4a284-f2da-4a85-8514-dc6639bf524b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "===== target=0.1 nF =====\n", "[0.1]\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "/tmp/ipykernel_91324/2722939843.py:53: TqdmDeprecationWarning: This function will be removed in tqdm==5.0.0\n", "Please use `tqdm.notebook.tqdm` instead of `tqdm.tqdm_notebook`\n", " for n_caps, program in tqdm.tqdm_notebook(programs):\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "bccaaaf304434c88aea8c793a7dc343c", "version_major": 2, "version_minor": 0 }, "text/plain": [ " 0%| | 0/1815 [00:00 0.011 * target and C < 2.5 * target]\n", " print([C for C, tol, cost in selected_values])\n", " for n_caps, program in tqdm.tqdm_notebook(programs):\n", " for capacitor_assignment in itertools.product(selected_values, repeat=n_caps):\n", " low, high = evaluate_tolerance(program, capacitor_assignment)\n", " if high < target * (1.0 + target_tolerance_high) and low > target*(1.0 - target_tolerance_low):\n", " if not any(math.isclose(low_cand, low) and math.isclose(high_cand, high) for _prog, _caps, low_cand, high_cand in found[target]):\n", " found[target].append([program, capacitor_assignment, low, high])\n", " print(f'{low=:.3f} nF {high=:.3f} nF {program=} {[C for C, tol, cost in capacitor_assignment]}')\n", " #break\n", " \n", " #if found[target]:\n", " # break" ] }, { "cell_type": "code", "execution_count": 6, "id": "a973a601-dad8-4e99-9fc2-de7fa8d0ef03", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[[0, 1, 2, '+', '+'],\n", " [0, 1, 2, '+', '*'],\n", " [0, 1, 2, '*', '+'],\n", " [0, 1, 2, '*', '*'],\n", " [0, 1, '+', 2, '+'],\n", " [0, 1, '+', 2, '*'],\n", " [0, 1, '*', 2, '+'],\n", " [0, 1, '*', 2, '*'],\n", " [0, 2, '+', 1, '+'],\n", " [0, 2, '+', 1, '*'],\n", " [0, 2, '*', 1, '+'],\n", " [0, 2, '*', 1, '*']]" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import formula_gen\n", "formula_gen.generate_formulas(3)" ] } ], "metadata": { "kernelspec": { "display_name": "decade-box", "language": "python", "name": "decade-box" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.11" } }, "nbformat": 4, "nbformat_minor": 5 }