mathviz 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ === 1.0.0 2009-12-23
2
+
3
+ * Rename from MatLat to MathViz
4
+ * Default output file name
5
+ * Add rdoc comments and examples
6
+ * Package as gem
7
+ * Included classes/modules under MathViz namespace
8
+
9
+ === 0.1.0 2009-12-18
10
+
11
+ * Support unit tracking
12
+ * Unary operators
13
+ * Moved operator out of box
14
+
15
+ === 0.0.1 2009-02-12
16
+
17
+ * Generate Graphs
18
+ * Track values
@@ -0,0 +1,16 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ examples/E_mc2.png
6
+ examples/E_mc2.rb
7
+ examples/dc.rb
8
+ examples/first.rb
9
+ examples/mathviz.rb
10
+ lib/mathviz.rb
11
+ spec/measurable_spec.rb
12
+ spec/measured_spec.rb
13
+ spec/operation_spec.rb
14
+ spec/spec_helper.rb
15
+ spec/unit_spec.rb
16
+ tasks/idocs.rake
@@ -0,0 +1,51 @@
1
+ -*- rdoc -*-
2
+
3
+ = MathViz
4
+
5
+ * http://github.com/JustinLove/mathviz
6
+
7
+ == DESCRIPTION:
8
+
9
+ Turn simple equations (a = b * c) into GraphViz dot files showing relationships, values, and units.
10
+
11
+ == FEATURES/PROBLEMS:
12
+
13
+ * Adds one method to Object and several to Numeric (some direct, some by Measurable and Unit.) If you use units of measure, each unit will appear on Numeric via module Unit.
14
+ * MathViz produces textual .dot files. You will need a viewer which supports dot files directly, or Graphviz to convert them to images yourself.
15
+
16
+ == SYNOPSIS:
17
+
18
+ require 'rubygems'
19
+ require 'mathviz'
20
+
21
+ # No units are provided by default.
22
+ # You can also use 1.unit(:m).per.unit(:s) etc.
23
+ module MathViz::Units
24
+ new_units :m, :s, :kg, :lb, :joule
25
+ end
26
+
27
+ MathViz.new {
28
+ # Alternate form: MathViz.new('optional_output_filename', binding_object)
29
+
30
+ m = 140.lb * 0.45359237.kg.per.lb
31
+ c = 299_792_458.m.per.s
32
+ # capitalized values are constants in ruby, so we need the underscore
33
+ _E = (m * (c * c)) * 1.joule.per.kg.m.m.per.s.s
34
+
35
+ binding # Don't forget to return the binding
36
+ }.dot # Required to produces the actual .dot file
37
+
38
+ link:examples/E_mc2.png
39
+ == REQUIREMENTS:
40
+
41
+ * GraphvizR (aka graphviz_r)
42
+ * Graphviz - http://www.graphviz.org/
43
+
44
+ == INSTALL:
45
+
46
+ * sudo gem install mathviz
47
+
48
+ == LICENSE:
49
+
50
+ Creative Commons Attribution-Share Alike 3.0 Unported Licence
51
+ http://creativecommons.org/licenses/by-sa/3.0/
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ gem 'hoe', '>= 2.1.0'
3
+ require 'hoe'
4
+ require 'fileutils'
5
+ require './lib/mathviz'
6
+
7
+ Hoe.plugin :newgem
8
+ # Hoe.plugin :website
9
+ # Hoe.plugin :cucumberfeatures
10
+
11
+ # Generate all the Rake tasks
12
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
13
+ $hoe = Hoe.spec 'mathviz' do
14
+ self.developer 'Justin Love', 'git@JustinLove.name'
15
+ self.rubyforge_name = self.name # TODO this is default value
16
+ self.extra_deps = [['GraphvizR','>= 0.5.1']]
17
+ self.extra_rdoc_files = ['README.rdoc']
18
+ end
19
+
20
+ require 'newgem/tasks'
21
+ Dir['tasks/**/*.rake'].each { |t| load t }
22
+
23
+ # TODO - want other tests/tasks run by default? Add them to the list
24
+ # remove_task :default
25
+ # task :default => [:spec, :features]
Binary file
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'mathviz'
3
+
4
+ # No units are provided by default.
5
+ # You can also use 1.unit(:m).per.unit(:s) etc.
6
+ module MathViz::Units
7
+ new_units :m, :s, :kg, :lb, :joule
8
+ end
9
+
10
+ MathViz.new {
11
+ # Alternate form: MathViz.new('optional_output_filename', binding_object)
12
+
13
+ m = 140.lb * 0.45359237.kg.per.lb
14
+ c = 299_792_458.m.per.s
15
+ # capitalized values are constants in ruby, so we need the underscore
16
+ _E = (m * (c * c)) * 1.joule.per.kg.m.m.per.s.s
17
+
18
+ binding # Don't forget to return the binding
19
+ }.dot # Required to produces the actual .dot file
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'mathviz'
3
+
4
+ module MathViz::Units
5
+ new_units :ms, :s, :frame
6
+ new_units :rev, :xx
7
+ new_units :pixel, :timel
8
+ end
9
+
10
+ MathViz.new {
11
+ pi = const 3.14159
12
+ second = 1000.ms.per.s
13
+
14
+ scale = input 1
15
+ diameter = 172.pixel
16
+ count = 10.xx.per.rev
17
+ unit = 1.per.xx
18
+ resolution = 100.ms
19
+ timeMultiplier = const 1
20
+ time = input((Time.now.to_f * 1000).floor).ms
21
+ frameRate = input 1.frame.per.s
22
+
23
+ root_position = time / resolution
24
+ unit_position = root_position / unit
25
+ un_position = unit_position / count
26
+
27
+ timels = unit * count
28
+ ms_rev = resolution * timels
29
+ rev_s = const(1000.ms.per.s) / ms_rev
30
+ real_rev_s = rev_s * timeMultiplier
31
+ rev_timel = const(1) / timels
32
+ configPerimeter = diameter * pi
33
+ perimeter = configPerimeter * scale
34
+ rev_pixel = const(1.rev) / perimeter
35
+ tick = rev_timel.min((rev_pixel * 1.pixel).max(rev_s * 1.s))
36
+ threshold = tick * 2
37
+ real_ms_rev = ms_rev / timeMultiplier
38
+ delay = tick * real_ms_rev
39
+
40
+ step = second / frameRate
41
+ passed = step * (timeMultiplier * 1.frame)
42
+ #passed = delay * timeMultiplier
43
+ ms = time + passed
44
+
45
+ root_to = ms / resolution
46
+ unit_to = root_to / unit
47
+ un_to = unit_to / count
48
+
49
+ delta = un_to - un_position
50
+ big = delta > threshold
51
+ visible = delta > rev_pixel
52
+ animatable = real_rev_s < 0.01.rev.per.s
53
+ jump = big & animatable
54
+ superfast = real_rev_s > 1.5.rev.per.s
55
+
56
+ binding
57
+ }.dot
58
+
@@ -0,0 +1,35 @@
1
+ require 'rubygems'
2
+ require 'mathviz'
3
+
4
+ MathViz.new('first') {
5
+ pi = const 3.14159
6
+
7
+ scale = input 1.0
8
+ size = const 172
9
+ count = const 60
10
+ unit = const 1
11
+ calcUnit = const 1000
12
+ timeMultiplier = const 1
13
+ time = input 859294
14
+ ms = time + 500
15
+
16
+ root_position = time / calcUnit
17
+ unit_position = root_position / unit
18
+ un_position = unit_position / count
19
+
20
+ root_to = ms / calcUnit
21
+ unit_to = root_to / unit
22
+ un_to = unit_to / count
23
+
24
+ relativeTime = count * unit * calcUnit
25
+ perimeter = size * scale * pi
26
+ pixel = const(1) / perimeter
27
+ tick = relativeTime * pixel
28
+ realTime = calcUnit.min(tick.max(1000))
29
+ delay = realTime / timeMultiplier
30
+ threashold = delay * 2
31
+ delta = un_to - un_position
32
+ timeDelta = relativeTime * delta
33
+
34
+ binding
35
+ }.dot
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'mathviz')
@@ -0,0 +1,594 @@
1
+ require 'rubygems'
2
+ require 'graphviz_r'
3
+
4
+ # Top level object.
5
+ class MathViz
6
+ # RubyGem version
7
+ VERSION = '1.0.0'
8
+
9
+ # Something to return instead of dividing by zero, etc.
10
+ Infinity = 1.0/0
11
+
12
+ # * base name of the output file. If omitted(falsy) it will use the top level program name.
13
+ # * Binding object, as if from 'binding'
14
+ # * A proc which returns a binding.
15
+ #
16
+ # If bind is passed, the proc will not be executed. If bind is falsy, the proc will be executed and it's return value stored.
17
+ #
18
+ # The proc is evaluated in the context of the new MathViz instance, making #const and #input directly available
19
+ def initialize(name = nil, bind = nil, &proc)
20
+ @name = name || File.basename($PROGRAM_NAME, '.rb')
21
+ @env = bind || instance_eval(&proc)
22
+ end
23
+
24
+ # Convert a basic value (typically Numeric) into a MathViz::Term (MathViz::Constant)
25
+ def const(x)
26
+ MathViz::Constant.new(x)
27
+ end
28
+
29
+ # Convert a basic value (typically Numeric) into a MathViz::Term (MathViz::Input)
30
+ def input(x)
31
+ MathViz::Input.new(x)
32
+ end
33
+
34
+ # Save a Graphviz .dot file in the current directory, with name specified in the constructor. Triggers most of the actual processsing.
35
+ def dot
36
+ MathViz::Term.name_terms!(@env)
37
+ #puts MathViz::Term.list_terms(@env).map {|t| t.long}
38
+ graph = GraphvizR.new @name
39
+ graph = MathViz::Term.list_terms(@env).inject(graph) {|g, t|
40
+ t.to_dot(g)
41
+ g
42
+ }
43
+
44
+ #puts graph.to_dot
45
+ graph.output(@name + '.dot', 'dot')
46
+ end
47
+ end
48
+
49
+ # Value objects that do the actual unit tracking.
50
+ #
51
+ # Contains all the interesting power tracking and cancellation.
52
+ class MathViz::Unit
53
+ # The interal representation. Current implementation method is hash-of-powers; e.g. {:m => 2, :s => -1} represents m*m/s
54
+ attr_reader :unit
55
+
56
+ # * With a symbol, creates a simple unit.
57
+ # * With a hash-of-powers, it simply copies those values.
58
+ # * Otherwise, it becomes a dimensionless unit.
59
+ def initialize(h = nil)
60
+ @unit = Hash.new(0)
61
+ case h
62
+ when Hash; @unit.merge!(h); normalize!
63
+ when Symbol: @unit[h] = 1
64
+ end
65
+ @unit.freeze
66
+ freeze
67
+ end
68
+
69
+ # Implement a simple binary operation. It verifies that the units match and returns the unit ERROR if not.
70
+ def binop(other)
71
+ if (unit != other.unit)
72
+ #p "#{to_s} !+- #{other.to_s}"
73
+ return MathViz::Unit.new(:ERROR)
74
+ end
75
+ return self
76
+ end
77
+
78
+ alias_method :+, :binop
79
+ alias_method :-, :binop
80
+ alias_method :<, :binop
81
+ alias_method :>, :binop
82
+ alias_method :==, :binop
83
+ alias_method :max, :binop
84
+ alias_method :min, :binop
85
+ alias_method :&, :binop
86
+ alias_method :|, :binop
87
+
88
+ def *(other)
89
+ x = @unit.dup
90
+ other.unit.each do |u,power|
91
+ x[u] += power
92
+ end
93
+ MathViz::Unit.new(x)
94
+ end
95
+
96
+ def /(other)
97
+ x = @unit.dup
98
+ other.unit.each do |u,power|
99
+ x[u] -= power
100
+ end
101
+ MathViz::Unit.new(x)
102
+ end
103
+
104
+ def numerator
105
+ unit.reject {|u,power| power < 0}
106
+ end
107
+
108
+ def denominator
109
+ unit.reject {|u,power| power > 0}
110
+ end
111
+
112
+ def to_s
113
+ n = stream(numerator)
114
+ d = stream(denominator)
115
+ return '' unless (n || d)
116
+ return "#{n||1}/#{d}" if d
117
+ return n
118
+ end
119
+
120
+ private
121
+
122
+ # Produce a string of multiplied terms
123
+ def stream(units)
124
+ x = units.map {|u,power| [u] * power.abs }.flatten.join('*')
125
+ if (x.empty?)
126
+ return nil
127
+ else
128
+ x
129
+ end
130
+ end
131
+
132
+ # Remove zero-powers
133
+ def normalize!
134
+ unit.reject! { |u,power| power == 0 }
135
+ self
136
+ end
137
+ end
138
+
139
+ # Common container for defined units.
140
+ #
141
+ # including MathViz::Units triggers extension by MathViz::Units::Class. MathViz::Units includes MathViz::Units::Class itself, so all those methods are available.
142
+ module MathViz::Units
143
+ # Provides the new_units class method to all classes with units
144
+ module Class
145
+ # Define new units (instance methods) on module MathViz::Units (where they will be picked up by everything including the module)
146
+ # Defined methods are essentialy aliases for #unit(name); see MathViz::Measurable / MathViz::Measured
147
+ def new_units(*units)
148
+ units.each do |u|
149
+ MathViz::Units.__send__ :define_method, u do
150
+ unit(u)
151
+ end
152
+ end
153
+ end
154
+
155
+ # extend MathViz::Units::Class
156
+ def included(host)
157
+ host.extend(MathViz::Units::Class)
158
+ end
159
+ end
160
+
161
+ extend MathViz::Units::Class
162
+ end
163
+
164
+ # Something (i.e. Numeric) which does not have MathViz::Units, but can be turned into something which does (i.e., MathViz::Constant)
165
+ module MathViz::Measurable
166
+ include MathViz::Units
167
+
168
+ # return constant wrapping self with the specified units; see also MathViz::Units::Class#new_units
169
+ def unit(x)
170
+ MathViz::Constant.new(self).unit(x)
171
+ end
172
+
173
+ # return constant wrapping self with new units assigned to the denominator
174
+ def per
175
+ MathViz::Constant.new(self).per
176
+ end
177
+ end
178
+
179
+ # Something (i.e. MathViz::Term) which has MathViz::Units.
180
+ module MathViz::Measured
181
+ include MathViz::Units
182
+
183
+ # Return a string representation of the units portion, with space if applicable
184
+ def with_units
185
+ u = units.to_s
186
+ if (u.empty?)
187
+ u
188
+ else
189
+ ' ' + u
190
+ end
191
+ end
192
+
193
+ # Add the named unit to our units and return self. See also MathViz::Units::Class#new_units
194
+ def unit(x)
195
+ @unit ||= MathViz::Unit.new
196
+ @unit_sign ||= 1
197
+ if (@unit_sign > 0)
198
+ @unit *= MathViz::Unit.new(x)
199
+ else
200
+ @unit /= MathViz::Unit.new(x)
201
+ end
202
+ self
203
+ end
204
+
205
+ # Statefull toggle numerator/denominator of unit assignment; e.g. m/s = .m.per.s
206
+ def per
207
+ @unit_sign ||= 1
208
+ @unit_sign *= -1
209
+ self
210
+ end
211
+
212
+ # attr_reader
213
+ def units
214
+ @unit || MathViz::Unit.new
215
+ end
216
+ end
217
+
218
+ class Numeric
219
+ include MathViz::Measurable
220
+
221
+ # Provide in operator form
222
+ def max(b)
223
+ [self, b].max
224
+ end
225
+
226
+ # Provide in operator form
227
+ def min(b)
228
+ [self, b].min
229
+ end
230
+
231
+ # Dummy defintion for all numerics. Normally only defined on things where it can possible return false.
232
+ def finite?
233
+ true
234
+ end
235
+ end
236
+
237
+ class Object
238
+ # Representation used for graphviz node names
239
+ def node
240
+ to_s
241
+ end
242
+ end
243
+
244
+ # Base class for graphable objects. It also contain the operators, which return MathViz::Operation subclasses.
245
+ class MathViz::Term
246
+ include MathViz::Measured
247
+
248
+ # Assign names to named MathViz::Terms, so the name can be effiently looked up from the MathViz::Term object.
249
+ def self.name_terms!(env)
250
+ eval("local_variables", env).each do |var|
251
+ value = eval(var, env)
252
+ if value.respond_to? :name=
253
+ value.name = var
254
+ end
255
+ end
256
+ end
257
+
258
+ # Return a list of all MathViz::Terms accessible from a binding
259
+ def self.list_terms(env)
260
+ eval("local_variables", env).map { |var|
261
+ value = eval(var, env)
262
+ if (value.kind_of?(MathViz::Term))
263
+ value
264
+ else
265
+ nil
266
+ end
267
+ }.compact
268
+ end
269
+
270
+ # Define op as a binary operator
271
+ def self.binop(op)
272
+ define_method(op) do |c|
273
+ MathViz::Operation::Binary.new(self, op, c)
274
+ end
275
+ end
276
+
277
+ # Define op as an unary operator
278
+ def self.unop(op)
279
+ define_method(op) do
280
+ MathViz::Operation::Unary.new(self, op)
281
+ end
282
+ end
283
+
284
+ # Graphviz node name; see MathViz::Term#name_terms!
285
+ attr_accessor :name
286
+
287
+ def to_s
288
+ @name || anon
289
+ end
290
+
291
+ # A string representation of the node's data, typically calculated value with units.
292
+ def data
293
+ f = to_f
294
+ if (f.kind_of?(TrueClass) || f.kind_of?(FalseClass))
295
+ f.to_s
296
+ elsif (!f.respond_to? :finite?)
297
+ f.to_s + with_units
298
+ elsif (!f.finite?)
299
+ MathViz::Infinity.to_s
300
+ elsif (f.floor == f)
301
+ f.to_i.to_s + with_units
302
+ else
303
+ f.to_s + with_units
304
+ end
305
+ end
306
+
307
+ def to_i
308
+ f = to_f
309
+ return MathViz::Infinity unless f.finite?
310
+ f.to_i
311
+ end
312
+
313
+ # Text label for graph nodes
314
+ def label
315
+ if (@name)
316
+ [data, node].join("\n")
317
+ else
318
+ data
319
+ end
320
+ end
321
+
322
+ # Graphviz node shape
323
+ def shape
324
+ :ellipse
325
+ end
326
+
327
+ # Graphviz node color
328
+ def color
329
+ :black
330
+ end
331
+
332
+ # Graphviz node line style
333
+ def style
334
+ if anonymous?
335
+ :dotted
336
+ elsif constant?
337
+ :solid
338
+ else
339
+ :dashed
340
+ end
341
+ end
342
+
343
+ # Extend Graphviz g with a representation of this object
344
+ def to_dot(g)
345
+ g[node] [:label => label, :shape => shape, :color => color, :style => style]
346
+ end
347
+
348
+ private
349
+ @@anon_master = 'A'
350
+
351
+ # Produces an unique name for #anonymous? nodes. Results are memoized for each instance.
352
+ def anon
353
+ if (@anon)
354
+ @anon
355
+ else
356
+ @anon = @@anon_master
357
+ @@anon_master = @@anon_master.succ
358
+ #puts "#{self.object_id} anon #{@anon}"
359
+ @anon
360
+ end
361
+ end
362
+
363
+ def anonymous?
364
+ !@name
365
+ end
366
+
367
+ public
368
+
369
+ ##
370
+ unop :floor
371
+
372
+ ##
373
+ unop :ceil
374
+
375
+
376
+ ##
377
+ binop :+
378
+
379
+ ##
380
+ binop :-
381
+
382
+ ##
383
+ binop :*
384
+
385
+ ##
386
+ binop :/
387
+
388
+ ##
389
+ binop :max
390
+
391
+ ##
392
+ binop :min
393
+
394
+ ##
395
+ binop :>
396
+
397
+ ##
398
+ binop :<
399
+
400
+ ##
401
+ binop :<=
402
+
403
+ ##
404
+ binop :>=
405
+
406
+ ##
407
+ binop :&
408
+
409
+ ##
410
+ binop :|
411
+
412
+ ##
413
+ binop :==
414
+ end
415
+
416
+ # A simple number.
417
+ #
418
+ # Also identifies the number as true constant, which affects nodes display style, so that opportunities for constant-folding can be idenified.
419
+ class MathViz::Constant < MathViz::Term
420
+ # wraps a primitive value
421
+ def initialize(a)
422
+ super()
423
+ @a = a
424
+ end
425
+
426
+ # Debugging method; string with both name and value
427
+ def long
428
+ n = @name && (@name + " = ")
429
+ "(#{n}#{to_f})"
430
+ end
431
+
432
+ # Forward to contained object
433
+ def to_f
434
+ @a.to_f
435
+ end
436
+
437
+ # Returns the units of the contained object (if any) or else it's own.
438
+ def units
439
+ if @a.respond_to? :units
440
+ @a.units
441
+ else
442
+ super
443
+ end
444
+ end
445
+
446
+ # Graphviz node shape
447
+ def shape
448
+ :plaintext
449
+ end
450
+
451
+ def constant?
452
+ true
453
+ end
454
+
455
+ # Forward to contained object
456
+ def finite?
457
+ @a.finite?
458
+ end
459
+ end
460
+
461
+ # A simple number.
462
+ #
463
+ # Derives most of it's behavior from MathViz::Constant, but also identifies the number as variable, which affects nodes display style, so that opportunities for constant-folding can be idenified.
464
+ class MathViz::Input < MathViz::Constant
465
+ # Graphiviz node shape
466
+ def shape
467
+ :ellipse
468
+ end
469
+
470
+ # false
471
+ def constant?
472
+ false
473
+ end
474
+ end
475
+
476
+ # Base class for MathViz::Operation::Binary and MathViz::Operation::Unary
477
+ class MathViz::Operation < MathViz::Term
478
+ # Turn the object into a MathViz::Term (MathViz::Constant) if isn't already a MathViz::Term. This allows for operator parameters to be primitive values without needing MathViz#const, MathViz#input, or units.
479
+ def term(x)
480
+ if (x.kind_of?(MathViz::Term))
481
+ x
482
+ else
483
+ MathViz::Constant.new(x)
484
+ end
485
+ end
486
+
487
+ # Graphviz node shape
488
+ def shape
489
+ :box
490
+ end
491
+
492
+ # Default Graphviz node color.
493
+ def color
494
+ :red
495
+ end
496
+ end
497
+
498
+ # Display and processing for single-value operators
499
+ class MathViz::Operation::Unary < MathViz::Operation
500
+ def initialize(a, op)
501
+ super()
502
+ @a = term(a)
503
+ @op = op
504
+ end
505
+
506
+ # Debugging method; return string of name and value.
507
+ def long
508
+ n = @name && (@name + " = ")
509
+ "(#{n}#{@a} #{@op} = #{to_f})"
510
+ end
511
+
512
+ # Extend Graphviz g with a representation of this object, and incoming connections
513
+ def to_dot(g)
514
+ super
515
+ (g[@a.node] >> g[node]) [:arrowhead => :normal, :headlabel => @op.to_s, :labeldistance => '2']
516
+ @a.to_dot(g) if (@a.respond_to?(:name) && @a.name.nil?)
517
+ end
518
+
519
+ # Apply the operator to create the derived value.
520
+ def to_f
521
+ return MathViz::Infinity unless @a.to_f.finite?
522
+ @a.to_f.__send__(@op)
523
+ end
524
+
525
+ # Forward to contained value
526
+ def units
527
+ @a.units
528
+ end
529
+
530
+ # Forward to contained value
531
+ def constant?
532
+ @a.constant?
533
+ end
534
+ end
535
+
536
+ # Display and processing for two-value operators
537
+ class MathViz::Operation::Binary < MathViz::Operation
538
+ def initialize(a, op, b)
539
+ super()
540
+ @a = term(a)
541
+ @op = op
542
+ @b = term(b)
543
+ end
544
+
545
+ # Debugging method; returns string of names and values
546
+ def long
547
+ n = @name && (@name + " = ")
548
+ "(#{n}#{@a} #{@op} #{@b} = #{to_f})"
549
+ end
550
+
551
+ # Graphviz node shape; differentiates comparison operators
552
+ def shape
553
+ if ([:>, :<, :>=, :<=, :&, :|, :==].include? @op)
554
+ :ellipse
555
+ else
556
+ :box
557
+ end
558
+ end
559
+
560
+ # Graphviz node color; differentiates basic mathematical operators (+, -, *, /)
561
+ def color
562
+ case @op
563
+ when :+: :green;
564
+ when :-: :yellow;
565
+ when :*: :blue;
566
+ when :/: :cyan;
567
+ else :red;
568
+ end
569
+ end
570
+
571
+ # Extend Graphviz g with a representation of this object, and incoming connections
572
+ def to_dot(g)
573
+ super
574
+ (g[@a.node] >> g[node]) [:arrowhead => :normal, :headlabel => @op.to_s, :labeldistance => '2']
575
+ (g[@b.node] >> g[node]) [:arrowhead => :onormal]
576
+ @a.to_dot(g) if (@a.respond_to?(:name) && @a.name.nil?)
577
+ @b.to_dot(g) if (@b.respond_to?(:name) && @b.name.nil?)
578
+ end
579
+
580
+ # Apply the operator to create the derived value.
581
+ def to_f
582
+ @a.to_f.__send__(@op, @b.to_f)
583
+ end
584
+
585
+ # Apply the operator to create the derived units.
586
+ def units
587
+ @a.units.__send__(@op, @b.units)
588
+ end
589
+
590
+ # True only if both operands are #constant?
591
+ def constant?
592
+ @a.constant? && @b.constant?
593
+ end
594
+ end
@@ -0,0 +1,11 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ module MathViz::Units
4
+ new_units :s
5
+ end
6
+
7
+ describe MathViz::Measured do
8
+ it "works on numbers" do
9
+ 1.s.data.should == "1 s"
10
+ end
11
+ end
@@ -0,0 +1,42 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ class Subject
4
+ include MathViz::Measured
5
+
6
+ new_units :s
7
+
8
+ def to_s
9
+ "1"
10
+ end
11
+ end
12
+
13
+ describe MathViz::Measured do
14
+ before do
15
+ @subject = Subject.new.s
16
+ end
17
+
18
+ it "should be created" do
19
+ @subject.should_not be_nil
20
+ end
21
+
22
+ it "should have a unit in it's string" do
23
+ @subject.with_units.should == " s"
24
+ end
25
+
26
+ it "should not have extra space if no unit" do
27
+ Subject.new.with_units.should == ""
28
+ end
29
+
30
+ it "can have different units" do
31
+ Subject.new.unit(:x).with_units.should == " x"
32
+ end
33
+
34
+ it "should create denominators" do
35
+ Subject.new.per.s.with_units.should == " 1/s"
36
+ end
37
+
38
+ it "should have a shorthand for new units" do
39
+ Subject.new_units(:m, :h)
40
+ Subject.new.m.per.h.with_units.should == " m/h"
41
+ end
42
+ end
@@ -0,0 +1,31 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ module MathViz::Units
4
+ new_units :s
5
+ end
6
+
7
+ describe MathViz::Operation do
8
+ describe MathViz::Operation::Unary do
9
+ it "can floor infinity" do
10
+ (MathViz::Constant.new(1.0/0).floor).data.should == "Infinity"
11
+ end
12
+
13
+ it "preserves units" do
14
+ (MathViz::Constant.new(1).s.floor).data.should == "1 s"
15
+ end
16
+ end
17
+
18
+ describe MathViz::Operation::Binary do
19
+ it "reports ints as ints" do
20
+ (MathViz::Constant.new(1) * 2).data.should == "2"
21
+ end
22
+
23
+ it "doesn't croak on divide by zero" do
24
+ (MathViz::Constant.new(220) / 0).data.should == "Infinity"
25
+ end
26
+
27
+ it "preserves units" do
28
+ (1.s * 2).data.should == "2 s"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
5
+ gem 'rspec'
6
+ require 'spec'
7
+ end
8
+
9
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
10
+ require 'mathviz'
@@ -0,0 +1,189 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ shared_examples_for "common combinations" do
4
+ it "can be created" do
5
+ @unit.should_not be_nil
6
+ end
7
+
8
+ it "flags a mismatch" do
9
+ (@unit + MathViz::Unit.new(:x)).to_s.should == 'ERROR'
10
+ end
11
+
12
+ it "adds with same" do
13
+ (@unit + @unit).to_s.should == @unit.to_s
14
+ end
15
+
16
+ it "multiplies with nothing" do
17
+ (@unit * MathViz::Unit.new).to_s.should == @unit.to_s
18
+ end
19
+
20
+ it "divides with nothing" do
21
+ (@unit / MathViz::Unit.new).to_s.should == @unit.to_s
22
+ end
23
+
24
+ it "cancels on division" do
25
+ (@unit / @unit).should.to_s == ''
26
+ end
27
+ end
28
+
29
+ describe "MathViz::Unit" do
30
+ context "with no argument" do
31
+ before do
32
+ @unit = MathViz::Unit.new
33
+ end
34
+
35
+ it_should_behave_like "common combinations"
36
+
37
+ it "has a blank representation" do
38
+ @unit.to_s.should == ''
39
+ end
40
+
41
+ it "multiplies with numerator" do
42
+ (@unit * MathViz::Unit.new(:x)).to_s.should == 'x'
43
+ end
44
+
45
+ it "divides with numerator" do
46
+ (@unit / MathViz::Unit.new(:x)).to_s.should == '1/x'
47
+ end
48
+
49
+ it "multiplies with denominator" do
50
+ (@unit * MathViz::Unit.new(:x => -1)).to_s.should == '1/x'
51
+ end
52
+
53
+ it "divides with denominator" do
54
+ (@unit / MathViz::Unit.new(:x => -1)).to_s.should == 'x'
55
+ end
56
+ end
57
+
58
+ context "with a single argument" do
59
+ before do
60
+ @unit = MathViz::Unit.new(:s)
61
+ end
62
+
63
+ it_should_behave_like "common combinations"
64
+
65
+ it "has a simple representation" do
66
+ @unit.to_s.should == "s"
67
+ end
68
+ end
69
+
70
+ context "with a numerator argument" do
71
+ before do
72
+ @unit = MathViz::Unit.new(:s => 1)
73
+ end
74
+
75
+ it_should_behave_like "common combinations"
76
+
77
+ it "has a simple representation" do
78
+ @unit.to_s.should == "s"
79
+ end
80
+
81
+ it "multiplies with numerator" do
82
+ ['s*x', 'x*s'].should include((@unit * MathViz::Unit.new(:x)).to_s)
83
+ end
84
+
85
+ it "divides with numerator" do
86
+ (@unit / MathViz::Unit.new(:x)).to_s.should == 's/x'
87
+ end
88
+
89
+ it "multiplies with denominator" do
90
+ (@unit * MathViz::Unit.new(:x => -1)).to_s.should == 's/x'
91
+ end
92
+
93
+ it "divides with denominator" do
94
+ ['s*x', 'x*s'].should include((@unit / MathViz::Unit.new(:x => -1)).to_s)
95
+ end
96
+
97
+ it "cancels on multiplication" do
98
+ (@unit * MathViz::Unit.new(:s => -1)).to_s.should == ''
99
+ end
100
+ end
101
+
102
+ context "with a numerator squared argument" do
103
+ before do
104
+ @unit = MathViz::Unit.new(:s => 2)
105
+ end
106
+
107
+ it_should_behave_like "common combinations"
108
+
109
+ it "has a double representation" do
110
+ @unit.to_s.should == "s*s"
111
+ end
112
+
113
+ it "multiplies with numerator" do
114
+ ['s*s*x', 'x*s*s'].should include((@unit * MathViz::Unit.new(:x)).to_s)
115
+ end
116
+
117
+ it "divides with numerator" do
118
+ (@unit / MathViz::Unit.new(:x)).to_s.should == 's*s/x'
119
+ end
120
+
121
+ it "multiplies with denominator" do
122
+ (@unit * MathViz::Unit.new(:x => -1)).to_s.should == 's*s/x'
123
+ end
124
+
125
+ it "divides with denominator" do
126
+ ['s*s*x', 'x*s*s'].should include((@unit / MathViz::Unit.new(:x => -1)).to_s)
127
+ end
128
+
129
+ it "cancels on multiplication" do
130
+ (@unit * MathViz::Unit.new(:s => -1)).to_s.should == 's'
131
+ end
132
+
133
+ it "increases on multiplication" do
134
+ (@unit * MathViz::Unit.new(:s => 1)).to_s.should == 's*s*s'
135
+ end
136
+ end
137
+
138
+ context "with a denominator argument" do
139
+ before do
140
+ @unit = MathViz::Unit.new(:s => -1)
141
+ end
142
+
143
+ it_should_behave_like "common combinations"
144
+
145
+ it "has a 1 in the numerator position" do
146
+ @unit.to_s.should == "1/s"
147
+ end
148
+
149
+ it "multiplies with numerator" do
150
+ ['x/s'].should include((@unit * MathViz::Unit.new(:x)).to_s)
151
+ end
152
+
153
+ it "divides with numerator" do
154
+ ['1/s*x', '1/x*s'].should include((@unit / MathViz::Unit.new(:x)).to_s)
155
+ end
156
+
157
+ it "multiplies with denominator" do
158
+ ['1/s*x', '1/x*s'].should include((@unit * MathViz::Unit.new(:x => -1)).to_s)
159
+ end
160
+
161
+ it "divides with denominator" do
162
+ ['x/s'].should include((@unit / MathViz::Unit.new(:x => -1)).to_s)
163
+ end
164
+
165
+ it "cancels on multiplication" do
166
+ (@unit * MathViz::Unit.new(:s => 1)).to_s.should == ''
167
+ end
168
+ end
169
+
170
+ context "with a complex argument" do
171
+ before do
172
+ @unit = MathViz::Unit.new(:V => 1, :A => 1, :h => -1)
173
+ end
174
+
175
+ it_should_behave_like "common combinations"
176
+
177
+ it "has a complex representation" do
178
+ ["V*A/h", "A*V/h"].should include(@unit.to_s)
179
+ end
180
+
181
+ it "cancels on multiplication" do
182
+ ["V*A", "A*V"].should include((@unit * MathViz::Unit.new(:h)).to_s)
183
+ end
184
+
185
+ it "cancels on multiplication" do
186
+ ["V/h"].should include((@unit * MathViz::Unit.new(:A => -1)).to_s)
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,4 @@
1
+ task :idocs => [:redocs, 'README.rdoc', 'examples/E_mc2.png'] do |t|
2
+ mkdir 'doc/examples'
3
+ cp 'examples/E_mc2.png', 'doc/examples/'
4
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mathviz
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Justin Love
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-25 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: GraphvizR
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.5.1
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.3.3
34
+ version:
35
+ description: Turn simple equations (a = b * c) into GraphViz dot files showing relationships, values, and units.
36
+ email:
37
+ - git@JustinLove.name
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - History.txt
44
+ - Manifest.txt
45
+ - README.rdoc
46
+ files:
47
+ - History.txt
48
+ - Manifest.txt
49
+ - README.rdoc
50
+ - Rakefile
51
+ - examples/E_mc2.png
52
+ - examples/E_mc2.rb
53
+ - examples/dc.rb
54
+ - examples/first.rb
55
+ - examples/mathviz.rb
56
+ - lib/mathviz.rb
57
+ - spec/measurable_spec.rb
58
+ - spec/measured_spec.rb
59
+ - spec/operation_spec.rb
60
+ - spec/spec_helper.rb
61
+ - spec/unit_spec.rb
62
+ - tasks/idocs.rake
63
+ has_rdoc: true
64
+ homepage: http://github.com/JustinLove/mathviz
65
+ licenses: []
66
+
67
+ post_install_message:
68
+ rdoc_options:
69
+ - --main
70
+ - README.rdoc
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ requirements: []
86
+
87
+ rubyforge_project: mathviz
88
+ rubygems_version: 1.3.5
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Turn simple equations (a = b * c) into GraphViz dot files showing relationships, values, and units.
92
+ test_files: []
93
+