cossincalc 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,73 @@
1
+ module CosSinCalc
2
+ class Triangle
3
+ class Formatter
4
+ UNIT_FACTORS = { :radian => Math::PI, :degree => 180.0, :gon => 200.0 }
5
+
6
+ attr_reader :triangle, :precision # References to associated triangle object and data precision.
7
+
8
+ def initialize(triangle, precision = 2)
9
+ @triangle = triangle
10
+ @precision = precision
11
+ end
12
+
13
+ # Converts an input value to a number.
14
+ def self.parse(value)
15
+ return value if value.is_a? Float
16
+ return nil if value.nil? || value.to_s !~ /\S/
17
+
18
+ value = value.to_s.gsub(/[^\d.,]+/, '').split(/[^\d]+/)
19
+ value.push('.' + value.pop) if value.length > 1
20
+ value.join.to_f
21
+ end
22
+
23
+ # Converts an input angle value in some unit to a radian number.
24
+ def self.parse_angle(value, from_unit)
25
+ value.is_a?(Float) ? value : convert_angle(parse(value), from_unit)
26
+ end
27
+
28
+ # Converts the given value from the unit specified to radians.
29
+ # If reverse is true, the value will be converted from radians to the specified unit.
30
+ def self.convert_angle(value, unit, reverse = false)
31
+ return nil if value.nil?
32
+
33
+ factor = (Math::PI / UNIT_FACTORS[unit.to_sym])
34
+ value * (reverse ? 1.0 / factor : factor)
35
+ end
36
+
37
+ # Returns the size of the angle to the given variable, rounded and converted to the prefered unit.
38
+ def angle(v)
39
+ format(Formatter.convert_angle(t.angle(v), t.angles.unit, true), @precision)
40
+ end
41
+
42
+ private
43
+ # Shorthand-method referencing the associated triangle object.
44
+ def t
45
+ @triangle
46
+ end
47
+
48
+ # Rounds the value down to the specified amount of decimals.
49
+ def round(value, decimals)
50
+ multiplier = 10 ** decimals
51
+ (value * multiplier).round.to_f / multiplier
52
+ end
53
+
54
+ # Formats the given value to improve human readability.
55
+ def format(value, decimals)
56
+ "%.#{decimals}f" % round(value, decimals)
57
+ end
58
+
59
+ # Returns the number of significant digits of the given value.
60
+ def significant_digits(number)
61
+ number.to_s.gsub(/^[0.]+/, '').length
62
+ end
63
+
64
+ def method_missing(name, *args)
65
+ if [:side, :altitude, :median, :angle_bisector, :area, :circumference].include?(name)
66
+ format(t.send(name, *args), @precision)
67
+ else
68
+ super(name, *args)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,134 @@
1
+ module CosSinCalc
2
+ class Triangle
3
+ class Formatter
4
+ class Latex
5
+ # Initializes the LaTeX renderer.
6
+ def initialize(formatter)
7
+ @formatter = formatter
8
+ @triangle = formatter.triangle
9
+ end
10
+
11
+ # Returns the generated LaTeX code.
12
+ def to_tex(filename = nil)
13
+ document(@triangle.alt ? (<<-EOT) : document_content(filename))
14
+ #{document_content(filename ? filename + '-1' : nil)}
15
+
16
+ \\newpage
17
+ \\section*{Alternative triangle}
18
+ Another triangle can be constructed based on the variables given.\\\\[0.2 cm]
19
+
20
+ #{Latex.new(@triangle.alt.humanize(f.precision)).document_content(filename ? filename + '-2' : nil)}
21
+ EOT
22
+ end
23
+
24
+ # Saves the generated LaTeX to a TeX file.
25
+ # The filename should be provided without the .tex extension.
26
+ def save_tex(filename)
27
+ File.open("#{filename}.tex", 'w') { |f| f.write(to_tex(filename)) }
28
+ end
29
+
30
+ # Saves the generated LaTeX to a TeX file and then converts it using pdflatex.
31
+ # The filename should be provided without the .pdf extension.
32
+ def save_pdf(filename)
33
+ save_tex(filename)
34
+ `pdflatex \"#{filename}.tex\" --output-directory #{File.dirname(filename)}`
35
+ end
36
+
37
+ # Returns the content of the LaTeX document.
38
+ def document_content(image_filename = nil)
39
+ variable_table + "\\\\[0.2 cm]\n\n" + equations + "\n\n" + drawing(image_filename)
40
+ end
41
+
42
+ private
43
+ # Shorthand-method referencing the associated formatter object.
44
+ def f
45
+ @formatter
46
+ end
47
+
48
+ # Wraps the variables calculated in a LaTeX table.
49
+ def variable_table
50
+ table = "\\begin{tabular}{ r r r r r }\n"
51
+ table << ['Angles', 'Sides', 'Altitudes', 'Medians', 'Angle bisectors'].join(' & ') + " \\\\ \\hline\n"
52
+
53
+ @triangle.each do |v|
54
+ table << [ "$#{v.to_s.upcase}=#{format_angle(f.angle(v), @triangle.angles.unit)}$", "$#{v}=#{f.side(v)}$",
55
+ "$h_#{v.to_s.upcase}=#{f.altitude(v)}$", "$m_#{v}=#{f.median(v)}$",
56
+ "$t_#{v.to_s.upcase}=#{f.angle_bisector(v)}$" ].join(' & ') + " \\\\\n"
57
+ end
58
+
59
+ table << "\\end{tabular}"
60
+ end
61
+
62
+ # Adds the appropriate unit to the angle value.
63
+ def format_angle(value, unit)
64
+ value + case unit
65
+ when :degree then '\degree'
66
+ when :gon then '\unit{gon}'
67
+ when :radian then '\unit{radian}'
68
+ end
69
+ end
70
+
71
+ # Formats the equation for embedding into a LaTeX document.
72
+ def format_equation(latex, variables, angle_unit)
73
+ latex.gsub! /@pi/, (angle_unit == :radian ? '\pi' :
74
+ format_angle(CosSinCalc::Triangle::Formatter::UNIT_FACTORS[angle_unit].to_i.to_s, angle_unit))
75
+
76
+ symbols = latex.gsub /[$@]\d/ do |match|
77
+ v = variables[match[1..-1].to_i - 1]
78
+ (match[0] == ?@ ? v.to_s.upcase : v.to_s)
79
+ end
80
+
81
+ values = latex.gsub /[$@]\d/ do |match|
82
+ v = variables[match[1..-1].to_i - 1]
83
+ (match[0] == ?@ ? format_angle(f.angle(v), angle_unit) : f.side(v))
84
+ end.split('=').reverse.join('=')
85
+
86
+ symbols + ' &= ' + values
87
+ end
88
+
89
+ # Returns the list of equations performed during calculation.
90
+ def equations
91
+ "\\begin{align*}\n" +
92
+ @triangle.equations.map { |latex, vars| format_equation(latex, vars, @triangle.angles.unit) }.join("\\\\\n") +
93
+ "\n\\end{align*}"
94
+ end
95
+
96
+ # Saves the associated drawing and returns the embedding LaTeX code.
97
+ # If no filename is given no image will be saved (can be used in testing).
98
+ def drawing(filename = nil)
99
+ CosSinCalc::Triangle::Drawing.new(f).save_png(filename) if filename
100
+ "\\begin{center}\n\\includegraphics[scale=0.4]{#{filename || 'placeholder'}}\n\\end{center}"
101
+ end
102
+
103
+ # Wraps the given LaTeX content into a LaTeX document ready to write to filesystem.
104
+ def document(content)
105
+ <<-EOT
106
+ \\documentclass{article}
107
+ \\usepackage{amsmath}
108
+ \\usepackage{amsfonts}
109
+ \\usepackage{graphicx}
110
+
111
+ \\DeclareMathSymbol{*}{\\mathbin}{symbols}{"01}
112
+ \\newcommand{\\unit}[1]{\\ensuremath{\\, \\mathrm{#1}}}
113
+ \\newcommand{\\degree}{\\ensuremath{^{\\circ}}}
114
+
115
+ % Uncomment to use a comma as the decimal separator (you would still write a period in the source).
116
+ % \\DeclareMathSymbol{,}{\\mathpunct}{letters}{"3B}
117
+ % \\DeclareMathSymbol{.}{\\mathord}{letters}{"3B}
118
+ % \\DeclareMathSymbol{\\decimal}{\\mathord}{letters}{"3A}
119
+
120
+ \\title{CosSinCalc Calculation Results}
121
+ \\author{Calculated by the CosSinCalc Triangle Calculator}
122
+
123
+ \\begin{document}
124
+ \\maketitle
125
+
126
+ #{content}
127
+
128
+ \\end{document}
129
+ EOT
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,87 @@
1
+ module CosSinCalc
2
+ class Triangle
3
+ class Validator
4
+ NOT_ENOUGH_VARIABLES = '3 values must be specified.'
5
+ TOO_MANY_VARIABLES = 'Only 3 values should be specified.'
6
+ NO_SIDES = 'At least one side must be given.'
7
+ INVALID_SIDE = 'Only numbers (above zero) are accepted values for a side.'
8
+ INVALID_ANGLE = 'Only numbers (above zero) are accepted values for an angle. Furthermore the angle must remain inside the scope of the sum of all angles in the triangle.'
9
+ INVALID_TRIANGLE = 'The specified values do not match a valid triangle.'
10
+ CALCULATOR_PRECISION = 0.01
11
+
12
+ class ValidationError < Exception
13
+ attr_reader :messages, :sides_valid, :angles_valid
14
+
15
+ def initialize(messages, sides_valid = {}, angles_valid = {})
16
+ @messages, @sides_valid, @angles_valid = messages, sides_valid, angles_valid
17
+ end
18
+ end
19
+
20
+ def initialize(triangle)
21
+ @triangle = triangle
22
+ end
23
+
24
+ # Validates the triangle before calculation and raises an exception on errors.
25
+ def validate
26
+ @sides_valid, @angles_valid = {}, {}
27
+
28
+ t.each do |v|
29
+ @sides_valid[v] = t.side(v).nil? || valid_side?(t.side(v))
30
+ @angles_valid[v] = t.angle(v).nil? || valid_angle?(t.angle(v))
31
+ end
32
+
33
+ errors = error_messages
34
+ errors.empty? || (raise ValidationError.new(errors, @sides_valid, @angles_valid))
35
+ end
36
+
37
+ # Checks whether the calculation was successful and the values given/calculated match a triangle.
38
+ def validate_calculation
39
+ t.each do |v, r|
40
+ raise ValidtionError.new([INVALID_TRIANGLE]) unless valid_side?(t.side(v)) && valid_angle?(t.angle(v))
41
+
42
+ angle = t.calculate_angle_by_sides(v, r)
43
+ unless angle > t.angle(v) - CALCULATOR_PRECISION && angle < t.angle(v) + CALCULATOR_PRECISION
44
+ raise ValidtionError.new([INVALID_TRIANGLE])
45
+ end
46
+ end
47
+ return true
48
+ end
49
+
50
+ private
51
+ # Shorthand-method referencing the associated triangle object.
52
+ def t
53
+ @triangle
54
+ end
55
+
56
+ # Returns an array of unique errors raised when validating the triangle.
57
+ def error_messages
58
+ errors = []
59
+ errors << INVALID_SIDE if @sides_valid.values.include?(false)
60
+ errors << INVALID_ANGLE if @angles_valid.values.include?(false)
61
+
62
+ if errors.empty?
63
+ errors << NOT_ENOUGH_VARIABLES if total_values < 3
64
+ errors << TOO_MANY_VARIABLES if total_values > 3
65
+ errors << NO_SIDES if t.sides.amount < 1
66
+ end
67
+
68
+ return errors
69
+ end
70
+
71
+ # Returns the total number of variables given.
72
+ def total_values
73
+ @total_values ||= t.sides.amount + t.angles.amount
74
+ end
75
+
76
+ # Returns whether the value is a valid side.
77
+ def valid_side?(value)
78
+ value.is_a?(Float) && value.finite? && value > 0
79
+ end
80
+
81
+ # Returns whether the value is a valid angle.
82
+ def valid_angle?(value)
83
+ valid_side?(value) && value < Math::PI
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,24 @@
1
+ module CosSinCalc
2
+ class Triangle
3
+ class VariableHash < Hash
4
+ attr_accessor :unit
5
+
6
+ # Initializes the variables.
7
+ def initialize
8
+ super()
9
+ CosSinCalc::Triangle::VARIABLES.each { |v| self[v] = nil }
10
+ end
11
+
12
+ # If a symbol is given, returns the associated value.
13
+ # If an array of symbols is given, returns an array of the associated values.
14
+ def [](vars)
15
+ vars.is_a?(Array) ? vars.map { |v| super(v) } : super(vars)
16
+ end
17
+
18
+ # Returns the amount of variables that have a value.
19
+ def amount
20
+ self.values.compact.size
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module CosSinCalc
2
+ VERSION = "1.0.0"
3
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cossincalc
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Molte Emil Strange Andersen
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-06-30 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: |
23
+ CosSinCalc is a web application able to calculate the variables of a triangle. The live site is located at http://cossincalc.com/. This is an offline version of the calculator.
24
+
25
+ You can use the included command line utility to generate a PDF page containing all the results, formulae and a drawing of the triangle, or you can include it as a library in your Ruby application and use just the features you care about.
26
+
27
+ email: molte@cossincalc.com
28
+ executables:
29
+ - cossincalc
30
+ extensions: []
31
+
32
+ extra_rdoc_files: []
33
+
34
+ files:
35
+ - MIT-LICENSE
36
+ - Rakefile
37
+ - lib/core_ext.rb
38
+ - lib/cossincalc/triangle/calculator.rb
39
+ - lib/cossincalc/triangle/drawing/svg.rb
40
+ - lib/cossincalc/triangle/drawing.rb
41
+ - lib/cossincalc/triangle/formatter/latex.rb
42
+ - lib/cossincalc/triangle/formatter.rb
43
+ - lib/cossincalc/triangle/validator.rb
44
+ - lib/cossincalc/triangle/variable_hash.rb
45
+ - lib/cossincalc/triangle.rb
46
+ - lib/cossincalc/version.rb
47
+ - lib/cossincalc.rb
48
+ - bin/cossincalc
49
+ - bin/trollop.rb
50
+ has_rdoc: true
51
+ homepage: http://cossincalc.com/
52
+ licenses: []
53
+
54
+ post_install_message:
55
+ rdoc_options: []
56
+
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ hash: 3
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ hash: 3
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ requirements: []
78
+
79
+ rubyforge_project:
80
+ rubygems_version: 1.3.7
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Triangle calculator
84
+ test_files: []
85
+