pulo 0.1.1 → 0.1.2

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.
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