pulo 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pulo.rb +28 -0
  3. data/lib/pulo/exceptions.rb +6 -0
  4. data/lib/pulo/figure/figure2d.rb +248 -0
  5. data/lib/pulo/figure/figure3d.rb +166 -0
  6. data/lib/pulo/formatting.rb +140 -0
  7. data/lib/pulo/frames/frame.rb +289 -0
  8. data/lib/pulo/frames/frame_cell.rb +83 -0
  9. data/lib/pulo/frames/frame_column.rb +149 -0
  10. data/lib/pulo/frames/frame_row.rb +87 -0
  11. data/lib/pulo/helpers.rb +1 -0
  12. data/lib/pulo/machine/hydraulics/pipe.rb +71 -0
  13. data/lib/pulo/machine/machines.rb +10 -0
  14. data/lib/pulo/machine/mechanics/moments_of_inertia.rb +64 -0
  15. data/lib/pulo/machine/steam/boiler.rb +61 -0
  16. data/lib/pulo/machine/steam/boiler_deaerator.rb +64 -0
  17. data/lib/pulo/machine/steam/deaerator.rb +37 -0
  18. data/lib/pulo/machine/steam/desuperheater.rb +29 -0
  19. data/lib/pulo/machine/steam/header.rb +20 -0
  20. data/lib/pulo/machine/steam/if97.rb +378 -0
  21. data/lib/pulo/machine/steam/steam_process.rb +27 -0
  22. data/lib/pulo/machine/steam/steam_turbine.rb +42 -0
  23. data/lib/pulo/machine/steam/water_steam.rb +229 -0
  24. data/lib/pulo/material/water.rb +34 -0
  25. data/lib/pulo/quantity/dimension.rb +63 -0
  26. data/lib/pulo/quantity/numeric_overloads.rb +217 -0
  27. data/lib/pulo/quantity/quantity.rb +285 -0
  28. data/lib/pulo/quantity/quantity_builder.rb +185 -0
  29. data/lib/pulo/quantity/quantity_definitions.rb +8 -0
  30. data/lib/pulo/quantity/quantity_definitions/area_volume.rb +73 -0
  31. data/lib/pulo/quantity/quantity_definitions/basic.rb +157 -0
  32. data/lib/pulo/quantity/quantity_definitions/electric.rb +22 -0
  33. data/lib/pulo/quantity/quantity_definitions/energy.rb +50 -0
  34. data/lib/pulo/quantity/quantity_definitions/fluids.rb +23 -0
  35. data/lib/pulo/quantity/quantity_definitions/force_power.rb +82 -0
  36. data/lib/pulo/quantity/quantity_definitions/rotation.rb +49 -0
  37. data/lib/pulo/quantity/quantity_definitions/value.rb +65 -0
  38. data/lib/pulo/quantity/quantity_definitions/velocity_acc_flow.rb +66 -0
  39. data/lib/pulo/quantity/quantity_groups/quantity_groups.rb +36 -0
  40. data/lib/pulo/quantity/unit.rb +45 -0
  41. data/lib/pulo/quantity_checker.rb +13 -0
  42. data/lib/pulo/tables/density.rb +10 -0
  43. data/lib/pulo/tables/melting_temperature.rb +9 -0
  44. data/lib/pulo/tables/specific_energy.rb +10 -0
  45. data/lib/pulo/tables/speed_of_sound.rb +9 -0
  46. data/lib/pulo/tables/tables.rb +104 -0
  47. data/lib/pulo/tables/tensile_strength.rb +10 -0
  48. data/lib/pulo/tables/yield_strength.rb +10 -0
  49. data/lib/pulo/version.rb +3 -0
  50. metadata +51 -3
@@ -0,0 +1,140 @@
1
+ # encoding: utf-8
2
+
3
+ module Pulo
4
+ class << self
5
+
6
+ def stats_format stats
7
+ ret=''
8
+ stats.each do |stat|
9
+ if stat[0]==:number
10
+ ret+='Count: ' + stat[1].to_s + "\n"
11
+ else
12
+ ret+=stat[0].to_s.capitalize + ': ' + stat[1].to_s + "\n"
13
+ end
14
+ end
15
+ ret
16
+ end
17
+
18
+ def super_digit(val)
19
+ val.to_s.chars.inject('') do |res, chr|
20
+ res+= case chr
21
+ when '.'
22
+ "\u207B".encode('utf-8')
23
+ when '-'
24
+ "\u207B".encode('utf-8')
25
+ when '1'
26
+ "\u00B9".encode('utf-8')
27
+ when '2'
28
+ "\u00B2".encode('utf-8')
29
+ when '3'
30
+ "\u00B3".encode('utf-8')
31
+ when '0'
32
+ "\u2070".encode('utf-8')
33
+ when '4'
34
+ "\u2074".encode('utf-8')
35
+ when '5'
36
+ "\u2075".encode('utf-8')
37
+ when '6'
38
+ "\u2076".encode('utf-8')
39
+ when '7'
40
+ "\u2077".encode('utf-8')
41
+ when '8'
42
+ "\u2078".encode('utf-8')
43
+ when '9'
44
+ "\u2079".encode('utf-8')
45
+ else
46
+ ''
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ class NumberToRoundedConverter
53
+ def self.convert(number, precision)
54
+
55
+ precision ||= Pulo.precision
56
+
57
+ if Pulo.significant_figures && precision > 0
58
+ digits, rounded_number = digits_and_rounded_number(number, precision)
59
+ precision -= digits
60
+ precision = 0 if precision < 0 # don't let it be negative
61
+ else
62
+ rounded_number = number.round(precision)
63
+ rounded_number = rounded_number.to_i if precision == 0
64
+ rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros
65
+ end
66
+ formatted_string = "%00.#{precision}f" % rounded_number
67
+
68
+ delimited_number = NumberToDelimitedConverter.convert(formatted_string)
69
+ format_number(delimited_number)
70
+ end
71
+
72
+ private
73
+
74
+ def self.digits_and_rounded_number(number,precision)
75
+ if zero?(number)
76
+ [1, 0]
77
+ else
78
+ digits = digit_count(number)
79
+ multiplier = 10 ** (digits - precision)
80
+ rounded_number = calculate_rounded_number(number,multiplier)
81
+ digits = digit_count(rounded_number) # After rounding, the number of digits may have changed
82
+ [digits, rounded_number]
83
+ end
84
+ end
85
+
86
+ def self.calculate_rounded_number(number,multiplier)
87
+ (number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier
88
+ end
89
+
90
+ def self.digit_count(number)
91
+ number.zero? ? 1 : (Math.log10(absolute_number(number)) + 1).floor
92
+ end
93
+
94
+ def self.format_number(number)
95
+ escaped_separator = Regexp.escape('.')
96
+ number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
97
+ end
98
+
99
+ def self.absolute_number(number)
100
+ number.respond_to?(:abs) ? number.abs : number.to_d.abs
101
+ end
102
+
103
+ def self.zero?(number)
104
+ number.respond_to?(:zero?) ? number.zero? : number.to_d.zero?
105
+ end
106
+ end
107
+
108
+ class NumberToDelimitedConverter
109
+ DELIMITED_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/
110
+
111
+ def self.convert(number)
112
+ parts(number).join('.')
113
+ end
114
+
115
+ private
116
+
117
+ def self.parts(number)
118
+ left, right = number.to_s.split('.')
119
+ left.gsub!(DELIMITED_REGEX) do |digit_to_delimit|
120
+ "#{digit_to_delimit},"
121
+ end
122
+ [left, right].compact
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ #class BigDecimal
129
+ # DEFAULT_STRING_FORMAT = 'F'
130
+ # def to_formatted_s(*args)
131
+ # if args[0].is_a?(Symbol)
132
+ # super
133
+ # else
134
+ # format = args[0] || DEFAULT_STRING_FORMAT
135
+ # _original_to_s(format)
136
+ # end
137
+ # end
138
+ # alias_method :_original_to_s, :to_s
139
+ # alias_method :to_s, :to_formatted_s
140
+ #end
@@ -0,0 +1,289 @@
1
+ require 'csv'
2
+ require 'descriptive_statistics'
3
+ require_relative 'frame_cell'
4
+ require_relative 'frame_row'
5
+ require_relative 'frame_column'
6
+
7
+ module Pulo
8
+
9
+ class Frame
10
+
11
+ attr_reader :column_count,:row_count,:rows, :columns, :column_names
12
+
13
+ def initialize
14
+ @rows=[]
15
+ @columns=[]
16
+ @column_names={}
17
+ @column_count=0
18
+ @row_count=0
19
+ end
20
+
21
+ def export_csv(path)
22
+
23
+ CSV.open(path, 'wb') do |csv|
24
+ csv << @columns.map {|col| col.name}
25
+ csv << @columns.map {|col|
26
+ if col.column_class.respond_to?(:quantity_name)
27
+ col.column_class.quantity_name
28
+ else
29
+ col.column_class.name
30
+ end
31
+ }
32
+ csv << @columns.map {|col|
33
+ if col.column_class.respond_to?(:quantity_name)
34
+ col.column_unit.abbreviation
35
+ else
36
+ ''
37
+ end
38
+ }
39
+ @rows.each do |row|
40
+ csv<<row.to_a.map{|cell|
41
+ if cell.class.respond_to?(:quantity_name)
42
+ cell.value
43
+ else
44
+ cell
45
+ end
46
+ }
47
+ end
48
+ end
49
+ end
50
+
51
+ #applies the given function to each row
52
+ #returns a hash where each element (keyed by the group) is a frame containing the group
53
+ def group(group_function)
54
+ #t=Time.now
55
+ groups={}
56
+ @rows.each do |row|
57
+ res=group_function.call(row)
58
+ if groups[res]
59
+ groups[res].append_row(row.to_a)
60
+ else
61
+ frm=self.copy_definition
62
+ frm.append_row(row.to_a)
63
+ groups.merge!({res=>frm})
64
+ end
65
+ end
66
+ #puts "Groups in #{((Time.now-t)*1000).to_i} ms."
67
+ groups
68
+ end
69
+
70
+ def group_reduce(group_function,column_defns)
71
+ #get groups
72
+ groups=group(group_function)
73
+
74
+ #t=Time.now
75
+ output=Frame.new
76
+ output.append_column 'Group'
77
+
78
+ #setup the columns
79
+ column_defns.keys.each do |col|
80
+ output.append_column(col)
81
+ end
82
+
83
+ #add a row to the output for each group produced in the earlier stage
84
+ groups.each do |group|
85
+ row=column_defns.map do |defn|
86
+ if defn[1].class==Proc
87
+ defn[1].call group[1]
88
+ else
89
+ #assume an array with method for descriptive statistics and a column name
90
+ c=group[1][defn[1][1]]
91
+ v=case defn[1][0]
92
+ when :min
93
+ c.to_a.min
94
+ when :max
95
+ c.to_a.max
96
+ else
97
+ DescriptiveStatistics.send(defn[1][0],c.to_a)
98
+ end
99
+ if c.column_class.respond_to?(:quantity_name) && defn[1][0]!=:number
100
+ c.column_class.send(c.column_unit.name,v)
101
+ else
102
+ v
103
+ end
104
+ end
105
+ end
106
+ row.insert 0,group[0]
107
+ output.append_row row
108
+ end
109
+ #puts "Reduce in #{((Time.now-t)*1000).to_i} ms."
110
+ output
111
+
112
+ end
113
+
114
+ def sort(&sort_function)
115
+ int=@rows.map do |row|
116
+ [row.row_number,sort_function.call(row)]
117
+ end.sort_by {|elm| elm[1]}.map{|elm| elm[0]}
118
+ frm=self.copy_definition
119
+ int.each do |row_no|
120
+ frm.append_row self.rows[row_no].to_a_values
121
+ end
122
+ frm
123
+ end
124
+
125
+ def clone
126
+ frame=copy_definition
127
+ @rows.each do |row|
128
+ frame.append_row row.to_a
129
+ end
130
+ frame
131
+ end
132
+
133
+ def copy_definition
134
+ frame=Frame.new
135
+ @columns.each do |col|
136
+ frame.append_column(col.name,col.hidden?,&col.formula)
137
+ end
138
+ frame
139
+ end
140
+
141
+ def delete_column(name_or_index)
142
+ if name_or_index.is_a?(Integer)
143
+ raise IndexError,"No column number #{name_or_index} defined." unless @columns[name_or_index]
144
+ index=name_or_index
145
+ name=@columns[index].name
146
+ else
147
+ index=@column_names[name_or_index]
148
+ raise IndexError,"Column with name '#{name_or_index}' not found." unless index
149
+ name=name_or_index
150
+ end
151
+ @rows.each {|row| row.delete_column(index)}
152
+ @column_names.delete(name)
153
+ @column_names.each do |item|
154
+ @column_names[item[0]]-=1 if @column_names[item[0]]>=index
155
+ end
156
+ @columns.delete_at(index)
157
+ @column_count-=1
158
+ end
159
+
160
+ def rename_column(old_name,new_name)
161
+ col=self[old_name]
162
+ col_no=@column_names[old_name]
163
+ @column_names.delete(old_name)
164
+ @column_names.merge!({new_name=>col_no})
165
+ col.name=new_name
166
+ end
167
+
168
+ def append_column(name,hidden=false,&formula)
169
+ col=FrameColumn.new(name,self, hidden, &formula)
170
+
171
+ @columns << col
172
+ @column_names.merge!({name=>@column_count})
173
+
174
+ @rows.each do |rw|
175
+ cell=FrameCell.new(col,rw)
176
+ col.append_row(cell)
177
+ rw.append_column(cell)
178
+ end
179
+ @column_count+=1
180
+ col
181
+ end
182
+
183
+ def append_row(values)
184
+ if values.is_a?(Array)
185
+ vals=values.each
186
+ row=FrameRow.new(self,@row_count)
187
+ @columns.each do |col|
188
+ if values.length==column_count
189
+ v = vals.next
190
+ else
191
+ v = col.value_column? ? vals.next : nil
192
+ end
193
+ cell=FrameCell.new(col,row,v)
194
+ col.append_row(cell)
195
+ row.append_column(cell)
196
+ end
197
+ @rows<<row
198
+ @row_count+=1
199
+ row
200
+ else
201
+ raise 'Expecting an array of values as the append row.'
202
+ end
203
+ end
204
+ def insert_row(previous_row,values)
205
+ if values.is_a?(Array)
206
+ row_no=previous_row.row_number+1
207
+ vals=values.each
208
+ row=FrameRow.new(self,row_no)
209
+ @columns.each do |col|
210
+ if values.length==column_count
211
+ v = vals.next
212
+ else
213
+ v = col.value_column? ? vals.next : nil
214
+ end
215
+ cell=FrameCell.new(col,row,v)
216
+ col.insert_row(row_no,cell)
217
+ row.append_column(cell)
218
+ end
219
+ @rows.insert(row_no,row)
220
+ @row_count+=1
221
+ @rows.drop(row_no+1).each {|r| r.row_number+=1}
222
+ else
223
+ raise 'Expecting an array of values as the append row.'
224
+ end
225
+ end
226
+
227
+ def recalc_all(with_timing=false)
228
+ formula_columns.each { |col| col.recalc with_timing }
229
+ end
230
+
231
+ def [](column)
232
+ if column.is_a?(Integer)
233
+ raise IndexError,"No column number #{column} defined." unless @columns[column]
234
+ @columns[column]
235
+ else
236
+ column_no=@column_names[column]
237
+ raise IndexError,"Column with name '#{column}' not found." unless column_no
238
+ @columns[column_no]
239
+ end
240
+ end
241
+
242
+ def first_row
243
+ @rows.first
244
+ end
245
+ def last_row
246
+ @rows.last
247
+ end
248
+
249
+ def lookup(value,column)
250
+ self[column].lookup(value)
251
+ end
252
+
253
+ def to_s(&filter)
254
+ @columns.each {|c| c.recalc_width}
255
+ ret="\n "
256
+ ret+=@columns.select {|c| !c.hidden?}.map{|s| s.name.ljust(s.width,' ')}.join(' ') + "\n"
257
+ ret+='========= '
258
+ ret+=@columns.select {|c| !c.hidden?}.map{|s| ''.ljust(s.width,'=')}.join('= ') + "\n"
259
+ ret+=' '
260
+ ret+=@columns.select {|c| !c.hidden?}.map{|s|
261
+ if s.column_class.respond_to?(:quantity_name)
262
+ (s.column_class.quantity_name + (s.value_column? ? '' : '*')).ljust(s.width,' ')
263
+ else
264
+ (s.column_class.to_s + (s.value_column? ? '' : '*')).ljust(s.width,' ')
265
+ end
266
+ }.join(' ') + "\n"
267
+ ret+='--------- '
268
+ ret+=@columns.select {|c| !c.hidden?}.map{|s| ''.ljust(s.width,'-')}.join('- ') + "\n"
269
+
270
+ ret+=(@rows.select{|row| !block_given? || filter.call(row)}.map { |row| row.to_s}).join("\n")
271
+
272
+ ret+="\n" + @rows.length.to_s + " rows\n"
273
+ ret
274
+ end
275
+
276
+ def inspect
277
+ "Frame Object"
278
+ end
279
+
280
+ def value_columns
281
+ @columns.find_all { |col| col.value_column?}
282
+ end
283
+
284
+ def formula_columns
285
+ @columns.find_all { |col| !col.value_column?}
286
+ end
287
+
288
+ end
289
+ end
@@ -0,0 +1,83 @@
1
+ module Pulo
2
+ class FrameCell
3
+
4
+ def initialize(parent_column,parent_row,value=nil)
5
+ @parent_column=parent_column
6
+ @parent_row=parent_row
7
+ @error=false
8
+ self.value=value
9
+ end
10
+
11
+ def column
12
+ @parent_column
13
+ end
14
+
15
+ def row
16
+ @parent_row
17
+ end
18
+
19
+ def empty?
20
+ @value.nil?
21
+ end
22
+
23
+ def value_column?
24
+ @parent_column.value_column?
25
+ end
26
+
27
+ def set_error
28
+ @error=true
29
+ end
30
+ def unset_error
31
+ @error=false
32
+ end
33
+ def error?
34
+ @error
35
+ end
36
+
37
+ def value
38
+ #raise "Column (#{@parent_column.name}) requires re-calc, value not available." if @parent_column.recalc_required
39
+ return "ERR" if error?
40
+ #return "UNK" if @parent_column.recalc_required
41
+ #return "NIL" if empty?
42
+ @value
43
+ end
44
+
45
+ def value=(val)
46
+
47
+ if @parent_column.column_class==NilClass
48
+ @parent_column.column_class=val.class
49
+ if val.class.respond_to?(:quantity_name)
50
+ @parent_column.column_unit=val.unit
51
+ end
52
+ end
53
+
54
+ if val.class.respond_to?(:quantity_name)
55
+ if val.unit.name!=@parent_column.column_unit.name
56
+ val=val.send(@parent_column.column_unit.name)
57
+ end
58
+ end
59
+
60
+ if @parent_column.column_class!=NilClass && val.class!=NilClass
61
+ if val.class.respond_to?(:quantity_name)
62
+ unless val.class.dimensions==@parent_column.column_class.dimensions
63
+ raise "Tried to set a value (#{val.to_s}) of class #{val.class} with dimensions #{val.class.dimensions} on column #{@parent_column.name} which already has a defined class of #{@parent_column.column_class}."
64
+ end
65
+ else
66
+ unless val.class==@parent_column.column_class
67
+ raise "Tried to set a value (#{val.to_s}) of class #{val.class} on column #{@parent_column.name} which already has a defined class of #{@parent_column.column_class}."
68
+ end
69
+ end
70
+ end
71
+ @value=val
72
+ end
73
+
74
+ def to_s
75
+ begin
76
+ @parent_column.formatter.call(value).ljust(@parent_column.width,' ')
77
+ rescue Exception => e
78
+ value.to_s
79
+ end
80
+ end
81
+
82
+ end
83
+ end