1 /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 
3 /*
4  Copyright (C) 2008 J. Erik Radmall
5  Copyright (C) 2009 StatPro Italia srl
6 
7  This file is part of QuantLib, a free-software/open-source library
8  for financial quantitative analysts and developers - http://quantlib.org/
9 
10  QuantLib is free software: you can redistribute it and/or modify it
11  under the terms of the QuantLib license.  You should have received a
12  copy of the license along with this program; if not, please email
13  <quantlib-dev@lists.sf.net>. The license is also available online at
14  <http://quantlib.org/license.shtml>.
15 
16  This program is distributed in the hope that it will be useful, but WITHOUT
17  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  FOR A PARTICULAR PURPOSE.  See the license for more details.
19 */
20 
21 #include <ql/experimental/commodities/unitofmeasureconversionmanager.hpp>
22 #include <ql/experimental/commodities/petroleumunitsofmeasure.hpp>
23 #include <ql/errors.hpp>
24 #include <algorithm>
25 
26 using namespace std;
27 
28 namespace QuantLib {
29 
30     namespace {
31 
matches(const UnitOfMeasureConversion & c1,const UnitOfMeasureConversion & c2)32         bool matches(const UnitOfMeasureConversion& c1,
33                      const UnitOfMeasureConversion& c2) {
34             return c1.commodityType() == c2.commodityType() &&
35                 ((c1.source() == c2.source() && c1.target() == c2.target())
36                  || (c1.source() == c2.target() && c1.target() == c2.source()));
37         }
38 
matches(const UnitOfMeasureConversion & c,const CommodityType & commodityType,const UnitOfMeasure & source,const UnitOfMeasure & target)39         bool matches(const UnitOfMeasureConversion& c,
40                      const CommodityType& commodityType,
41                      const UnitOfMeasure& source,
42                      const UnitOfMeasure& target) {
43             return c.commodityType() == commodityType &&
44                 ((c.source() == source && c.target() == target)
45                  || (c.source() == target && c.target() == source));
46         }
47 
matches(const UnitOfMeasureConversion & c,const CommodityType & commodityType,const UnitOfMeasure & source)48         bool matches(const UnitOfMeasureConversion& c,
49                      const CommodityType& commodityType,
50                      const UnitOfMeasure& source) {
51             return c.commodityType() == commodityType &&
52                 (c.source() == source || c.target() == source);
53         }
54 
55     }
56 
UnitOfMeasureConversionManager()57     UnitOfMeasureConversionManager::UnitOfMeasureConversionManager() {
58         addKnownConversionFactors();
59     }
60 
add(const UnitOfMeasureConversion & c)61     void UnitOfMeasureConversionManager::add(const UnitOfMeasureConversion& c) {
62         // not fast, but hopefully we won't have a lot of entries.
63         for (list<UnitOfMeasureConversion>::iterator i = data_.begin();
64              i != data_.end(); ++i) {
65             if (matches(*i, c)) {
66                 data_.erase(i);
67                 break;
68             }
69         }
70 
71         data_.push_back(c);
72     }
73 
lookup(const CommodityType & commodityType,const UnitOfMeasure & source,const UnitOfMeasure & target,UnitOfMeasureConversion::Type type) const74     UnitOfMeasureConversion UnitOfMeasureConversionManager::lookup(
75                                    const CommodityType& commodityType,
76                                    const UnitOfMeasure& source,
77                                    const UnitOfMeasure& target,
78                                    UnitOfMeasureConversion::Type type) const {
79         if (type == UnitOfMeasureConversion::Direct) {
80             return directLookup(commodityType,source,target);
81         } else if (!source.triangulationUnitOfMeasure().empty()) {
82             const UnitOfMeasure& link = source.triangulationUnitOfMeasure();
83             if (link == target)
84                 return directLookup(commodityType,source,link);
85             else
86                 return UnitOfMeasureConversion::chain(
87                                       directLookup(commodityType,source,link),
88                                       lookup(commodityType,link,target));
89         } else if (!target.triangulationUnitOfMeasure().empty()) {
90             const UnitOfMeasure& link = target.triangulationUnitOfMeasure();
91             if (source == link)
92                 return directLookup(commodityType,link,target);
93             else
94                 return UnitOfMeasureConversion::chain(
95                                      lookup(commodityType,source,link),
96                                      directLookup(commodityType,link,target));
97         } else {
98             return smartLookup(commodityType,source,target);
99         }
100     }
101 
clear()102     void UnitOfMeasureConversionManager::clear() {
103         data_.clear();
104         addKnownConversionFactors();
105     }
106 
addKnownConversionFactors()107     void UnitOfMeasureConversionManager::addKnownConversionFactors() {
108         add(UnitOfMeasureConversion(NullCommodityType(),
109                                     MBUnitOfMeasure(),
110                                     BarrelUnitOfMeasure(),
111                                     1000));
112         add(UnitOfMeasureConversion(NullCommodityType(),
113                                     BarrelUnitOfMeasure(),
114                                     GallonUnitOfMeasure(),
115                                     42));
116         add(UnitOfMeasureConversion(NullCommodityType(),
117                                     GallonUnitOfMeasure(),
118                                     MBUnitOfMeasure(),
119                                     1000 * 42));
120         add(UnitOfMeasureConversion(NullCommodityType(),
121                                     LitreUnitOfMeasure(),
122                                     GallonUnitOfMeasure(),
123                                     3.78541));
124         add(UnitOfMeasureConversion(NullCommodityType(),
125                                     BarrelUnitOfMeasure(),
126                                     LitreUnitOfMeasure(),
127                                     158.987));
128         add(UnitOfMeasureConversion(NullCommodityType(),
129                                     KilolitreUnitOfMeasure(),
130                                     BarrelUnitOfMeasure(),
131                                     6.28981));
132         add(UnitOfMeasureConversion(NullCommodityType(),
133                                     TokyoKilolitreUnitOfMeasure(),
134                                     BarrelUnitOfMeasure(),
135                                     6.28981));
136     }
137 
directLookup(const CommodityType & commodityType,const UnitOfMeasure & source,const UnitOfMeasure & target) const138     UnitOfMeasureConversion UnitOfMeasureConversionManager::directLookup(
139                                            const CommodityType& commodityType,
140                                            const UnitOfMeasure& source,
141                                            const UnitOfMeasure& target) const {
142 
143         for (list<UnitOfMeasureConversion>::const_iterator i = data_.begin();
144              i != data_.end(); ++i) {
145             if (matches(*i, commodityType, source, target)) {
146                 return *i;
147             }
148         }
149 
150         // Here, the code used to look for conversions with null
151         // commodity type as a fall-back.  However, this would only
152         // affect direct lookups and not other matches being tried in
153         // the smart-lookup loop.  To implement the complete fall-back
154         // strategy, we should either duplicate the loop (as we would
155         // duplicate it here---smelly) or change the 'matches'
156         // functions so that a null commodity type matches. However,
157         // in the second case we would also have to take care that
158         // conversions with a null type be at the end of the list so
159         // that they don't supersede specific types. We'll have to
160         // think a bit about this, so no fall-back for the time being.
161 
162         QL_FAIL("no direct conversion available from "
163                 << commodityType.code() << " " << source.code()
164                 << " to " << target.code());
165     }
166 
smartLookup(const CommodityType & commodityType,const UnitOfMeasure & source,const UnitOfMeasure & target,list<string> forbidden) const167     UnitOfMeasureConversion UnitOfMeasureConversionManager::smartLookup(
168                               const CommodityType& commodityType,
169                               const UnitOfMeasure& source,
170                               const UnitOfMeasure& target,
171                               list<string> forbidden) const {
172 
173         try {
174             return directLookup(commodityType,source,target);
175         } catch (Error&) {
176             ; // no direct conversion available; turn to smart lookup.
177         }
178 
179         // The source unit is forbidden to subsequent lookups in order
180         // to avoid cycles.
181         forbidden.push_back(source.code());
182 
183         for (list<UnitOfMeasureConversion>::const_iterator i = data_.begin();
184              i != data_.end(); ++i) {
185             // we look for conversion data which involve our source unit...
186             if (matches(*i, commodityType, source)) {
187                 const UnitOfMeasure& other =
188                     source == i->source() ? i->target() : i->source();
189                 if (find(forbidden.begin(),forbidden.end(),
190                          other.code()) == forbidden.end()) {
191                     // if we can get to the target from here...
192                     try {
193                         UnitOfMeasureConversion tail =
194                             smartLookup(commodityType,other,target);
195                         // ..we're done.
196                         return UnitOfMeasureConversion::chain(*i,tail);
197                     } catch (Error&) {
198                         // otherwise, we just discard this conversion.
199                         ;
200                     }
201                 }
202             }
203         }
204 
205         // if the loop completed, we have no way to return the
206         // requested conversion.
207         QL_FAIL("no conversion available for "
208                 << commodityType.code() << " from "
209                 << source.code() << " to " << target.code());
210     }
211 
212 }
213 
214