File Coverage

lib/Tribology/Lubricant.pm
Criterion Covered Total %
statement 125 150 83.3
branch 34 54 62.9
condition 10 23 43.4
subroutine 15 18 83.3
pod 8 10 80.0
total 192 255 75.2


line stmt bran cond sub pod time code
1             package Tribology::Lubricant;
2              
3             =head1 NAME
4              
5             Tribology::Lubricant - Data type that represents a Lubricant class.
6              
7             =head1 DESCRIPTION
8              
9             This class, given technical data based on lubricant TDS/PDS documents assists in calculation of various Rheologic characteristics of the lubricant. Such as:
10              
11             =over 4
12              
13             =item *
14              
15             V-T behavior (C) using B equation
16              
17             =item *
18              
19             Calculate viscosity at a given temperature ( C ) when any of two calibration points are known
20              
21             =item *
22              
23             Calculate viscosity index using ASTM D2270's B and B procedures ( C )
24              
25             =item *
26              
27             Lookup B and B constants of the lubricant using ASTM D2270 Table and using linear interoplation whenever neccessary ( C )
28              
29             =back
30              
31             =head1 SYNOPSIS
32              
33             require Tribology::Lubricant;
34              
35             # We already have viscosity at 40C and 100C.
36             my $lub = Tribology::Lubricant->new({
37             label => "Naphthenic spindle oil",
38             visc40 => 30,
39             visc100 => 100
40             });
41              
42             # Viscosity @ 50C:
43             my $visc50 = $lub->visc(50);
44              
45             # Viscosity index (VI)
46             my $vi = $lub->vi;
47              
48             # Viscosity-temperature constant:
49             my $vtc = $lub->vtc;
50              
51             # m-value, aka V-T behavior coefficient
52             my $m = $lub->m;
53              
54             # To draw the V-T (hyperbolic) graph of this particular lubricant we can generate data-points, say, from -20 to +100:
55              
56             my @data_points;
57             for my $T(-20..100) {
58             push @data_points, [$T, $lub->visc($T)];
59             }
60              
61             # Now you may pass @data_points to either GDGrap(Perl) or Highcharts(JS).
62              
63             =cut
64              
65 1     1   55584 use strict;
  1         1  
  1         22  
66 1     1   3 use warnings;
  1         1  
  1         18  
67 1     1   3 use Carp;
  1         1  
  1         1389  
68              
69             our $VERSION = '0.03';
70              
71             =head2 new(\%attr)
72              
73             Constructor. Following attributes (all optional) can be passed:
74              
75             =over 4
76              
77             =item label
78              
79             Arbitrary label of the lubricant. Used in graph data or report tables, charts
80              
81             =item visc40, visc100
82              
83             Viscosity @ 40 and 100 degrees Celcius.
84              
85             =item vi
86              
87             Viscosity index of the lubricant.
88              
89             =item density
90              
91             Specific gravity of the lubricant at a given temperature point. Must be passed a hashref of Temprature-Density values. Density
92             must be in kg/cm3.
93              
94             =back
95              
96             B C and C are just convenience attributes, since they are most widely given in product TDSs.
97             If you don't have calibration points at these temperatures IGNORE these attributes. Instead,
98             create empty constructor, set the calibration values you already have using C method. Such as:
99              
100             my $lubricant = Tribology::Lubricant->new({label => "Hypothetical lubricant"});
101             $lubricant->visc(50, 80);
102             $lubricant->visc(100, 5.23);
103              
104             =cut
105              
106             sub new {
107 9     9 1 1961 my ( $class, $arg ) = @_;
108              
109 9   100     26 $arg ||= {};
110              
111 9         34 my %internals = (
112             __visc_calibration_points => {},
113             %$arg
114             );
115              
116 9 100       26 if ( defined( $internals{visc40} ) ) {
117 7         14 $internals{__visc_calibration_points}{40} = $internals{visc40};
118             }
119              
120 9 50       25 if ( defined $internals{visc100} ) {
121 0         0 $internals{__visc_calibration_points}{100} = $internals{visc100};
122             }
123 9         26 return bless( \%internals, $class );
124             }
125              
126             =head2 label($new_label)
127              
128             Returns and/or sets label of the lubricant
129              
130             =cut
131              
132             sub label {
133 16     16 1 250 my ( $self, $new_label ) = @_;
134              
135 16 50       25 if ($new_label) {
136 0         0 $self->{label} = $new_label;
137             }
138 16         49 return $self->{label};
139             }
140              
141             =head2 visc($T, $cst)
142              
143             Given temperature ($T) in celcius returns kinematic viscosity in cst. If such value was not given to the constructor it attempts to calculate
144             this number using B equation. For this to be possible at least two calibration points must be given to C or two calibration
145             points must be set using two-argument syntax of C.
146              
147             If second argument is passed sets the viscosity point and returns the value $cst as is.
148              
149             # 1.10 (eni), bo'yi ( 2.27 )
150              
151             =cut
152              
153             sub visc {
154 7127     7127 1 336930 my ( $self, $T, $cst ) = @_;
155              
156 7127 50       8583 unless ( defined $T ) {
157 0         0 croak "visc(): usage error";
158             }
159              
160 7127 100       7794 if ( defined $cst ) {
161 18         36 $self->{__visc_calibration_points}{$T} = $cst;
162 18         45 return $cst;
163             }
164              
165             # If this calibration point was already given just return it:
166 7109 100       10464 if ( my $visc = $self->{__visc_calibration_points}{$T} ) {
167 5715         19438 return $visc;
168             }
169              
170             # We have to attempt to calculate the viscosity at this given temp
171              
172 1394         1216 my @calibrations = @{ $self->__calibration_points(2) };
  1394         2042  
173 1393 50       2402 unless ( scalar(@calibrations) == 2 ) {
174 0         0 croak "visc(): Not enough calibration points to complete Ubbelohde-Walter equation";
175             }
176              
177 1393         1496 my $c1_temp = $calibrations[0][0];
178 1393         1279 my $c1_cst = $calibrations[0][1];
179              
180 1393         1110 my $c2_temp = $calibrations[1][0];
181 1393         1330 my $c2_cst = $calibrations[1][1];
182              
183 1393         1911 my $vtc = $self->vtc;
184              
185             #my $b = ( log( log( $c1_cst + $vtc ) ) - log( log( $c2_cst + $vtc ) ) ) / ( log($c2_temp) - log($c1_temp) );
186 1393         1892 my $m = $self->m;
187 1393         2695 my $a = log( log( $c2_cst + $vtc ) / log(10) ) / log(10) + ( $m * log($c2_temp) / log(10) );
188              
189             #carp "\$c2_cst = $c2_cst";
190             #carp "\$c2_temp = $c2_temp";
191             #carp "\$a = $a";
192             #carp "\$m = $m";
193             #carp "\$vtc = $vtc";
194              
195 1393         1313 my $c = $a * 100;
196 1393         1181 my $d = $m * 25;
197              
198 1393         1824 my $visc = exp(1)**( log(10) * exp(1)**( ( ( $c * log(10) ) / 100 ) - ( ( $d * log( __c2k($T) ) ) / 25 ) ) ) - 0.7;
199              
200 1393         2735 $self->{__visc_calibration_points}{$T} = $visc;
201 1393         2947 return $visc;
202             } ## end sub visc
203              
204             =head2 m()
205              
206             Heart of the B equation. This is the coeffient that characterises V-T behavior of oils. It's a double-logarithmic V-T graph slope.
207             It requires at least two calibration points be present, or must be calculatable to work. Otherwise it throws error (croaks).
208              
209             =cut
210              
211             sub m {
212 1407     1407 1 1301 my $self = shift;
213              
214 1407         1534 my $calibrations = $self->__calibration_points(2);
215              
216 1407 50       2043 unless ( scalar @$calibrations == 2 ) {
217 0         0 croak "m(): at least 2 calibration points are required to properly calculate V-T slope (m).";
218             }
219              
220 1407         1384 my $c1_temp = $calibrations->[0][0];
221 1407         1253 my $c1_cst = $calibrations->[0][1];
222              
223 1407         1192 my $c2_temp = $calibrations->[1][0];
224 1407         1381 my $c2_cst = $calibrations->[1][1];
225            
226             #carp "c1_temp = $c1_temp\nc1_cst=$c1_cst\n";
227             #carp "c2_temp = $c2_temp\nc2_cst=$c2_cst\n";
228              
229 1407         1610 my $vtc = $self->vtc;
230 1407         5109 return ( log( log( $c1_cst + $vtc ) ) - log( log( $c2_cst + $vtc ) ) ) / ( log($c2_temp) - log($c1_temp) ) ;
231             }
232              
233             =head2 LH()
234              
235             Returns B and B values for the given lubricant. For this method to work lubricant's viscosity @ 100C must be known or calculatable.
236              
237             =cut
238              
239             sub LH {
240 31     31 1 42 my ($self) = @_;
241              
242 31         42 my $cst100 = $self->visc(100);
243 31 50       47 unless ($cst100) {
244 0         0 croak "__LH(): Viscosity of the lubricant \@ 100C is unknown. Cannot proceed further";
245             }
246              
247 31 50       60 if ( $cst100 < 2 ) {
248 0         0 croak "__LH(): ASTM D2270 does not define VI for lubricants below 2cst \@ 100C";
249             }
250              
251 31 100       58 if ( $cst100 > 70 ) {
252 1         5 my $L = ( 0.8353 * ( $cst100**2 ) ) + 14.67 * $cst100 - 216;
253 1         3 my $H = ( 0.1684 * ( $cst100**2 ) ) + 11.85 * $cst100 - 97;
254 1         3 return ( $L, $H );
255             }
256              
257 30         31 my ( @one_before, @one_after );
258 30         330 seek( DATA, 0, 0 );
259 30         504 while ( my $line = ) {
260 17745 50       18623 next unless length($line);
261 17745 100       19082 next if $line =~ m/^#/;
262 17625 100       26637 next if $line =~ m/^\s*$/;
263              
264 12945         19860 my ( $visc, $L, $H ) = $line =~ m/^
265             ([\d\.]+) \s+ ([\d\.]+) \s+ ([\d\.]+)
266             \s* $/x;
267              
268 12945 50 66     25544 next unless ( $visc && $L && $H );
      66        
269 3225 100       4913 return ( $L, $H ) if ( $visc == $cst100 );
270 3212 100       3533 if ( $visc < $cst100 ) {
271 3195         5173 @one_before = ( $visc, $L, $H );
272 3195         5483 next;
273             }
274 17 50       24 if ( $visc > $cst100 ) {
275 17         27 push @one_after, $visc, $L, $H;
276 17         19 last;
277             }
278             }
279              
280 17 50 33     37 unless ( @one_before && @one_after ) {
281 0         0 croak "__LH(): has nothing to interoplate";
282             }
283              
284 17         22 my $visc1 = $one_before[0];
285 17         15 my $visc2 = $one_after[0];
286 17         23 my $visc_delta = $visc2 - $visc1;
287              
288 17         17 my $L1 = $one_before[1];
289 17         16 my $L2 = $one_after[1];
290 17         31 my $L_delta = $L2 - $L1;
291              
292 17         18 my $H1 = $one_before[2];
293 17         18 my $H2 = $one_after[2];
294 17         20 my $H_delta = $H2 - $H1;
295              
296 17         18 my $L1_per_unit = $L_delta / $visc_delta;
297 17         15 my $H1_per_unit = $H_delta / $visc_delta;
298              
299 17         20 my $L = $L1 + ( ( $cst100 - $visc1 ) * $L1_per_unit );
300 17         18 my $H = $H1 + ( ( $cst100 - $visc1 ) * $H1_per_unit );
301              
302 17         46 return ( $L, $H );
303             } ## end sub LH
304              
305              
306             sub data_table {
307 0     0 0 0 my $self = shift;
308            
309 0         0 my @data = ();
310 0         0 seek( DATA, 0, 0 );
311 0         0 while ( my $line = ) {
312 0 0       0 next unless length($line);
313 0 0       0 next if $line =~ m/^#/;
314 0 0       0 next if $line =~ m/^\s*$/;
315              
316 0         0 my ( $visc, $L, $H ) = $line =~ m/^
317             ([\d\.]+) \s+ ([\d\.]+) \s+ ([\d\.]+)
318             \s* $/x;
319              
320 0 0 0     0 next unless ( $visc && $L && $H );
      0        
321            
322 0         0 push @data, [$visc, $L, $H];
323             }
324 0         0 return \@data;
325             }
326              
327             =head2 vi()
328              
329             Returns viscosity index of the lubricant, if such is possible. Remember, for this to be possible
330             calibration points at 40C and 100C must be available or calculatble. If it's impossible, it returns undef and writes a warning
331             to STDERR. When checking for error you must check for C at return.
332              
333             =cut
334              
335             sub vi {
336 17     17 1 60 my ($self) = @_;
337              
338 17         30 my $vi = $self->__vi_lt_100;
339 16 100       35 if ( $vi > 100 ) {
340 12         30 $vi = $self->__vi_gt_100;
341             }
342 16         54 return $vi;
343             }
344              
345             sub log10 {
346 36     36 0 43 my ($n) = @_;
347 36         77 return ( log($n) / log(10) );
348             }
349              
350             =head2 vtc()
351              
352             Returns B used in B equation to better differentiate V-T behavior
353             when the influence of temperature is low. This constant must be used to accurately (or properly) calculate C.
354             To calculate this value properly we need to have calibration points at 40C and 100C. If either of these points are missing
355             C defaults to 0.8.
356              
357             =cut
358              
359             sub vtc {
360 2816     2816 1 2648 my $self = shift;
361              
362 2816         2714 my $visc40 = $self->{__visc_calibration_points}{40};
363 2816         2587 my $visc100 = $self->{__visc_calibration_points}{100};
364              
365 2816 100 66     6446 return 0.8 unless ( $visc40 && $visc100 );
366 2815         5864 return ( $visc40 - $visc100 ) / $visc40 ;
367             }
368              
369             =head2 is_mineral
370              
371             Based on the C constant or C attempts to guess if current instance represents a mineral oil.
372              
373             =cut
374              
375             sub is_mineral {
376 0     0 1 0 my $self = shift;
377 0         0 my $vi = $self->vi;
378 0         0 die "work in progress";
379             }
380              
381             =head1 INTERNALS
382              
383             =head2 __c2k($T)
384              
385             Given temperature in celcius converts it to Kelvin
386              
387             =cut
388              
389             sub __c2k {
390 6994     6994   7769 my ($c) = @_;
391 6994 100       8523 unless (defined $c) {
392 1         10 die "__c2k(): usage error";
393             }
394 6993         11786 return ( $c + 273.15 );
395             }
396              
397             =head2 __k2c($T)
398              
399             Given temperature in Kelvin converts it to celcius
400              
401             =cut
402              
403             sub __k2c {
404 0     0   0 my ($k) = @_;
405 0         0 return ( $k - 273.15 );
406             }
407              
408             =head2 __calibration_points($limit)
409              
410             Returns all known calibration points to the lubricant as array reference. If C<$limit> is given limits the result set to that many points.
411             The points are guaranteed to be in ascending order by temperature. All temperature points are converted to Kelvin, since that's
412             what all internal formulas rely on.
413              
414             =cut
415              
416             sub __calibration_points {
417 2801     2801   3049 my ( $self, $limit ) = @_;
418              
419 2801         2441 my @calibrations = ( sort { $a <=> $b } keys %{ $self->{__visc_calibration_points} } );
  1612116         1198864  
  2801         27593  
420              
421 2801         9605 my $lowest_temp = $calibrations[0];
422 2801         2472 my $highest_temp = $calibrations[-1];
423              
424             return [
425 2801         3330 [ __c2k($lowest_temp), $self->visc($lowest_temp) ],
426             [ __c2k($highest_temp), $self->visc($highest_temp) ]
427             ];
428             }
429              
430             =head2 __vi_lt_100()
431              
432             Uses algorithm described in B<5. Procedure A> section of ASTM D2270. When you use C it invokes either method
433             accordingly.
434              
435             =cut
436              
437             sub __vi_lt_100 {
438 17     17   25 my ($self) = @_;
439              
440 17         25 my $cst40 = $self->visc(40);
441 16         23 my $cst100 = $self->visc(100);
442              
443 16 50 33     54 unless ( $cst40 && $cst100 ) {
444 0         0 croak "vi2(): viscosities at 40C and 100C must be known or calculatable";
445             }
446              
447 16         28 my ( $L, $H ) = $self->LH;
448 16         38 my $vi = ( ( $L - $cst40 ) / ( $L - $H ) ) * 100;
449              
450 16         60 return sprintf( "%d", $vi );
451             }
452              
453             =head2 __vi_gt_100()
454              
455             Uses algorithm described in B<6. Procedure B> section of ASTM D2270. When you use C it invokes either method
456             accordingly.
457              
458             =cut
459              
460             sub __vi_gt_100 {
461 12     12   19 my ($self) = @_;
462              
463 12         20 my ( undef, $H ) = $self->LH;
464 12         36 my $cst40 = $self->visc(40);
465 12         18 my $cst100 = $self->visc(100);
466              
467 12         24 my $N = ( log10($H) - log10($cst40) ) / log10($cst100);
468 12         31 my $vi = ( ( ( 10**$N ) - 1 ) / 0.00715 ) + 100;
469 12         43 return sprintf( "%d", $vi );
470             }
471              
472             =head1 SEE ALSO
473              
474             L, Second Edition by Wiley-VCH
475              
476             =cut
477              
478             1;
479              
480             __DATA__