Skip to content

Instantly share code, notes, and snippets.

@Mon-Ouie
Created March 23, 2014 18:40
Show Gist options
  • Save Mon-Ouie/9727561 to your computer and use it in GitHub Desktop.
Save Mon-Ouie/9727561 to your computer and use it in GitHub Desktop.
class Solver
BaseCapacitors = [1e-9, 100e-9, 1e-6, 9e-9, 12e-9]
Capacitors = BaseCapacitors +
BaseCapacitors.product(BaseCapacitors).map { |a, b| a*b/(a+b) } +
BaseCapacitors.product(BaseCapacitors).map { |a, b| a+b }
Capacitors.uniq!
BaseResistors = [100.0, 1e3, 10e3, 100e3, 1e6,
2.2e3,
3.3e3]
Resistors = BaseResistors +
BaseResistors.product(BaseResistors).map { |a, b| a*b/(a+b) } +
BaseResistors.product(BaseResistors).map { |a, b| a+b }
Resistors.uniq!
Prefixes = [[1e24, "Y"],
[1e21, "Z"],
[1e18, "E"],
[1e15, "P"],
[1e12, "T"],
[1e9, "G"],
[1e6, "M"],
[1e3, "k"],
[1e-3, "m"],
[1e-6, "μ"],
[1e-9, "n"],
[1e-12, "p"],
[1e-15, "f"],
[1e-18, "a"],
[1e-21, "z"],
[1e-24, "y"]]
Value = Struct.new(:value, :unit) do
def to_s
val, prefix = prefixed_value
("%.2f %s%s" % [val, prefix, unit]).strip
end
def prefixed_value
if 1e-2 <= value && value < 1e3
[value, nil]
else
Prefixes.each do |threshold, c|
if value >= threshold
return [value / threshold, c]
end
end
[value, nil]
end
end
end
EqualityConstraint = Struct.new(:var, :expected, :opts) do
def error(solver)
(solver.get(var) - expected).abs
end
def respected?(solver)
error(solver) <= if opts[:abs]
opts[:abs]
elsif opts[:rel]
opts[:rel] * expected.abs
end
end
def error_string(solution)
actual = solution.variables[var].value
unit = solution.variables[var].unit
abs = (actual - expected).abs
rel = abs/expected
"#{Value.new(abs, unit)} (#{"%2d" % (rel*100)}%)"
end
end
Solution = Struct.new(:variables, :constraints) do
def get(var)
variables[var].value
end
end
def self.solve(&block)
components = block.parameters.map { |x| x[1] }
solutions = new(components, &block).solve
sorted_solutions = solutions.sort_by { |s|
s.constraints.map { |c| c.error(s) }
}
direct, other = sorted_solutions.partition { |s|
components.all? { |c|
if c.to_s.start_with? "r"
BaseResistors.include? s.get(c)
elsif c.to_s.start_with? "c"
BaseCapacitors.include? s.get(c)
end
}
}
TablePrinter.print_solutions direct, other
end
def initialize(components, &block)
@components = components
@block = block
@solutions = []
@constraints = []
@variables = {}
end
def attempt(values)
values.each do |var, val|
@variables[var] = val
end
instance_exec(*@components.map { |v| get(v) }, &@block)
if @constraints.all? { |c| c.respected? self }
@solutions << Solution.new(@variables, @constraints)
else
end
clear
end
def solve
solve_with({}, @components)
@solutions
end
def solve_with(values, components)
if components.empty?
attempt(values)
else
comp = components[0]
comp_name = comp.to_s
rest = components[1..-1]
candidates, unit = if comp_name.start_with? "r"
[Resistors, "Ω"]
else
[Capacitors, "F"]
end
candidates.each do |val|
values = values.dup
values[comp] = Value.new(val, unit)
solve_with(values, rest)
end
end
end
def clear
@variables = {}
@constraints = []
end
def let(var, val, unit = nil)
@variables[var] = Value.new(val, unit)
end
def get(var)
@variables[var].value
end
def constraint_eq(name, val, opts)
@constraints << EqualityConstraint.new(name, val, opts)
end
def respond_to_missing?(name, include_all)
@variables.include? name || super
end
def method_missing(name, *args, &block)
if @variables.include? name
get name
else
super
end
end
end
module TablePrinter
module_function
def column_sizes(header, data)
max = data.inject header.map(&:size) do |sizes, row|
row.map(&:size).zip(sizes).map(&:max)
end
max.map { |n| n + 2 }
end
def format_row(row, sizes, sep, align = :right)
sep + row.zip(sizes).map { |t, s|
if align == :center
t.center(s)
else
t.rjust(s-1) + " "
end
}.join(sep) + sep
end
def print_table(header, direct, other)
vert = "│"
horiz = "─"
top_left = "┌"
top_right = "┐"
top_inter = "┬"
bot_left = "└"
bot_right = "┘"
bot_inter = "┴"
left_inter = "├"
right_inter = "┤"
cross = "┼"
sizes = column_sizes(header, direct + other)
top_sep = top_left + sizes.map { |s| horiz * s }.join(top_inter) + top_right
bot_sep = bot_left + sizes.map { |s| horiz * s }.join(bot_inter) + bot_right
inter_sep = left_inter + sizes.map { |s| horiz * s }.join(cross) +
right_inter
puts top_sep
puts format_row(header, sizes, vert, :center)
puts inter_sep
unless direct.empty?
direct.each do |row|
puts format_row(row, sizes, vert)
end
puts inter_sep unless other.empty?
end
unless other.empty?
other.each do |row|
puts format_row(row, sizes, vert)
end
end
puts bot_sep
end
def print_solutions(direct, other)
if direct.empty? && other.empty?
puts "No set of components satisfy the constraints!"
else
first = direct[0] || other[0]
header = first.variables.map { |var, val| var.to_s }
header += first.constraints.map { |c| "Δ#{c.var}" }
direct = direct.map { |s|
s.variables.map { |var, val| val.to_s } + s.constraints.map { |c|
c.error_string(s)
}
}
other = other.map { |s|
s.variables.map { |var, val| val.to_s } + s.constraints.map { |c|
c.error_string(s)
}
}
print_table(header, direct, other)
end
end
end
module Kernel
module_function
def solve(&block)
Solver.solve(&block)
end
end
solve do |r_a, r_b, c|
v_cc = 15
v_drop = 0.7
t_off = 0.7 * r_b * c
t_on = Math.log(2)/Math.log((2*v_cc - 3*v_drop)/(v_cc - 3*v_drop)) * r_a * c
t = t_on + t_off
let :f, 1/t, "Hz"
let :duty_cycle, t_on/t
constraint_eq :f, 40e3, :abs => 1e3
constraint_eq :duty_cycle, 0.5, :abs => 0.1
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment