Add string converters for KDM formulations.
[libdcp.git] / src / types.cc
1 /*
2     Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
6     libdcp is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     libdcp is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with libdcp.  If not, see <http://www.gnu.org/licenses/>.
18
19     In addition, as a special exception, the copyright holders give
20     permission to link the code of portions of this program with the
21     OpenSSL library under certain conditions as described in each
22     individual source file, and distribute linked combinations
23     including the two.
24
25     You must obey the GNU General Public License in all respects
26     for all of the code used other than OpenSSL.  If you modify
27     file(s) with this exception, you may extend this exception to your
28     version of the file(s), but you are not obligated to do so.  If you
29     do not wish to do so, delete this exception statement from your
30     version.  If you delete this exception statement from all source
31     files in the program, then also delete it here.
32 */
33
34
35 /** @file  src/types.cc
36  *  @brief Miscellaneous types
37  */
38
39
40 #include "compose.hpp"
41 #include "dcp_assert.h"
42 #include "exceptions.h"
43 #include "raw_convert.h"
44 #include "types.h"
45 #include "warnings.h"
46 LIBDCP_DISABLE_WARNINGS
47 #include <libxml++/libxml++.h>
48 LIBDCP_ENABLE_WARNINGS
49 #include <boost/algorithm/string.hpp>
50 #include <string>
51 #include <vector>
52 #include <cmath>
53 #include <cstdio>
54 #include <iomanip>
55
56
57 using std::string;
58 using std::ostream;
59 using std::vector;
60 using namespace dcp;
61 using namespace boost;
62
63
64 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
65 {
66         return (a.width == b.width && a.height == b.height);
67 }
68
69
70 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
71 {
72         return !(a == b);
73 }
74
75
76 /** Construct a Fraction from a string of the form "numerator denominator"
77  *  e.g. "1 3".
78  */
79 Fraction::Fraction (string s)
80 {
81         vector<string> b;
82         split (b, s, is_any_of (" "));
83         if (b.size() != 2) {
84                 boost::throw_exception (XMLError("malformed fraction " + s + " in XML node"));
85         }
86         numerator = raw_convert<int> (b[0]);
87         denominator = raw_convert<int> (b[1]);
88 }
89
90
91 string
92 Fraction::as_string () const
93 {
94         return String::compose ("%1 %2", numerator, denominator);
95 }
96
97
98 bool
99 dcp::operator== (Fraction const & a, Fraction const & b)
100 {
101         return (a.numerator == b.numerator && a.denominator == b.denominator);
102 }
103
104
105 bool
106 dcp::operator!= (Fraction const & a, Fraction const & b)
107 {
108         return (a.numerator != b.numerator || a.denominator != b.denominator);
109 }
110
111
112 Colour::Colour ()
113 {
114
115 }
116
117
118 Colour::Colour (int r_, int g_, int b_)
119         : r (r_)
120         , g (g_)
121         , b (b_)
122 {
123
124 }
125
126
127 Colour::Colour (string argb_hex)
128 {
129         int alpha;
130         if (sscanf (argb_hex.c_str(), "%2x%2x%2x%2x", &alpha, &r, &g, &b) != 4) {
131                 boost::throw_exception (XMLError ("could not parse colour string"));
132         }
133 }
134
135
136 string
137 Colour::to_argb_string () const
138 {
139         char buffer[9];
140         snprintf (buffer, sizeof(buffer), "FF%02X%02X%02X", r, g, b);
141         return buffer;
142 }
143
144
145 string
146 Colour::to_rgb_string () const
147 {
148         char buffer[7];
149         snprintf (buffer, sizeof(buffer), "%02X%02X%02X", r, g, b);
150         return buffer;
151 }
152
153
154 bool
155 dcp::operator== (Colour const & a, Colour const & b)
156 {
157         return (a.r == b.r && a.g == b.g && a.b == b.b);
158 }
159
160
161 bool
162 dcp::operator!= (Colour const & a, Colour const & b)
163 {
164         return !(a == b);
165 }
166
167
168 string
169 dcp::effect_to_string (Effect e)
170 {
171         switch (e) {
172         case Effect::NONE:
173                 return "none";
174         case Effect::BORDER:
175                 return "border";
176         case Effect::SHADOW:
177                 return "shadow";
178         }
179
180         boost::throw_exception (MiscError("unknown effect type"));
181 }
182
183
184 Effect
185 dcp::string_to_effect (string s)
186 {
187         if (s == "none") {
188                 return Effect::NONE;
189         } else if (s == "border") {
190                 return Effect::BORDER;
191         } else if (s == "shadow") {
192                 return Effect::SHADOW;
193         }
194
195         boost::throw_exception (ReadError("unknown subtitle effect type"));
196 }
197
198
199 string
200 dcp::halign_to_string (HAlign h)
201 {
202         switch (h) {
203         case HAlign::LEFT:
204                 return "left";
205         case HAlign::CENTER:
206                 return "center";
207         case HAlign::RIGHT:
208                 return "right";
209         }
210
211         boost::throw_exception (MiscError("unknown subtitle halign type"));
212 }
213
214
215 HAlign
216 dcp::string_to_halign (string s)
217 {
218         if (s == "left") {
219                 return HAlign::LEFT;
220         } else if (s == "center") {
221                 return HAlign::CENTER;
222         } else if (s == "right") {
223                 return HAlign::RIGHT;
224         }
225
226         boost::throw_exception (ReadError("unknown subtitle halign type"));
227 }
228
229
230 string
231 dcp::valign_to_string (VAlign v)
232 {
233         switch (v) {
234         case VAlign::TOP:
235                 return "top";
236         case VAlign::CENTER:
237                 return "center";
238         case VAlign::BOTTOM:
239                 return "bottom";
240         }
241
242         boost::throw_exception (MiscError("unknown subtitle valign type"));
243 }
244
245
246 VAlign
247 dcp::string_to_valign (string s)
248 {
249         if (s == "top") {
250                 return VAlign::TOP;
251         } else if (s == "center") {
252                 return VAlign::CENTER;
253         } else if (s == "bottom") {
254                 return VAlign::BOTTOM;
255         }
256
257         boost::throw_exception (ReadError("unknown subtitle valign type"));
258 }
259
260
261 string
262 dcp::direction_to_string (Direction v)
263 {
264         switch (v) {
265         case Direction::LTR:
266                 return "ltr";
267         case Direction::RTL:
268                 return "rtl";
269         case Direction::TTB:
270                 return "ttb";
271         case Direction::BTT:
272                 return "btt";
273         }
274
275         boost::throw_exception (MiscError("unknown subtitle direction type"));
276 }
277
278
279 Direction
280 dcp::string_to_direction (string s)
281 {
282         if (s == "ltr" || s == "horizontal") {
283                 return Direction::LTR;
284         } else if (s == "rtl") {
285                 return Direction::RTL;
286         } else if (s == "ttb" || s == "vertical") {
287                 return Direction::TTB;
288         } else if (s == "btt") {
289                 return Direction::BTT;
290         }
291
292         boost::throw_exception (ReadError("unknown subtitle direction type"));
293 }
294
295
296 /** Convert a content kind to a string which can be used in a
297  *  &lt;ContentKind&gt; node
298  *  @param kind ContentKind
299  *  @return string
300  */
301 string
302 dcp::content_kind_to_string (ContentKind kind)
303 {
304         switch (kind) {
305         case ContentKind::FEATURE:
306                 return "feature";
307         case ContentKind::SHORT:
308                 return "short";
309         case ContentKind::TRAILER:
310                 return "trailer";
311         case ContentKind::TEST:
312                 return "test";
313         case ContentKind::TRANSITIONAL:
314                 return "transitional";
315         case ContentKind::RATING:
316                 return "rating";
317         case ContentKind::TEASER:
318                 return "teaser";
319         case ContentKind::POLICY:
320                 return "policy";
321         case ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT:
322                 return "psa";
323         case ContentKind::ADVERTISEMENT:
324                 return "advertisement";
325         case ContentKind::EPISODE:
326                 return "episode";
327         case ContentKind::PROMO:
328                 return "promo";
329         }
330
331         DCP_ASSERT (false);
332 }
333
334
335 /** Convert a string from a &lt;ContentKind&gt; node to a libdcp ContentKind.
336  *  Reasonably tolerant about varying case
337  *  @param kind Content kind string
338  *  @return libdcp ContentKind
339  */
340 dcp::ContentKind
341 dcp::content_kind_from_string (string kind)
342 {
343         transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
344
345         if (kind == "feature") {
346                 return ContentKind::FEATURE;
347         } else if (kind == "short") {
348                 return ContentKind::SHORT;
349         } else if (kind == "trailer") {
350                 return ContentKind::TRAILER;
351         } else if (kind == "test") {
352                 return ContentKind::TEST;
353         } else if (kind == "transitional") {
354                 return ContentKind::TRANSITIONAL;
355         } else if (kind == "rating") {
356                 return ContentKind::RATING;
357         } else if (kind == "teaser") {
358                 return ContentKind::TEASER;
359         } else if (kind == "policy") {
360                 return ContentKind::POLICY;
361         } else if (kind == "psa") {
362                 return ContentKind::PUBLIC_SERVICE_ANNOUNCEMENT;
363         } else if (kind == "advertisement") {
364                 return ContentKind::ADVERTISEMENT;
365         } else if (kind == "episode") {
366                 return ContentKind::EPISODE;
367         } else if (kind == "promo") {
368                 return ContentKind::PROMO;
369         }
370
371         throw BadContentKindError (kind);
372 }
373
374
375 string
376 dcp::marker_to_string (dcp::Marker m)
377 {
378         switch (m) {
379         case Marker::FFOC:
380                 return "FFOC";
381         case Marker::LFOC:
382                 return "LFOC";
383         case Marker::FFTC:
384                 return "FFTC";
385         case Marker::LFTC:
386                 return "LFTC";
387         case Marker::FFOI:
388                 return "FFOI";
389         case Marker::LFOI:
390                 return "LFOI";
391         case Marker::FFEC:
392                 return "FFEC";
393         case Marker::LFEC:
394                 return "LFEC";
395         case Marker::FFMC:
396                 return "FFMC";
397         case Marker::LFMC:
398                 return "LFMC";
399         }
400
401         DCP_ASSERT (false);
402 }
403
404
405 dcp::Marker
406 dcp::marker_from_string (string s)
407 {
408         if (s == "FFOC") {
409                 return Marker::FFOC;
410         } else if (s == "LFOC") {
411                 return Marker::LFOC;
412         } else if (s == "FFTC") {
413                 return Marker::FFTC;
414         } else if (s == "LFTC") {
415                 return Marker::LFTC;
416         } else if (s == "FFOI") {
417                 return Marker::FFOI;
418         } else if (s == "LFOI") {
419                 return Marker::LFOI;
420         } else if (s == "FFEC") {
421                 return Marker::FFEC;
422         } else if (s == "LFEC") {
423                 return Marker::LFEC;
424         } else if (s == "FFMC") {
425                 return Marker::FFMC;
426         } else if (s == "LFMC") {
427                 return Marker::LFMC;
428         }
429
430         DCP_ASSERT (false);
431 }
432
433
434 ContentVersion::ContentVersion ()
435         : id ("urn:uuid:" + make_uuid())
436 {
437
438 }
439
440
441 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
442         : id(node->string_child("Id"))
443         , label_text(node->string_child("LabelText"))
444 {
445
446 }
447
448
449 ContentVersion::ContentVersion (string label_text_)
450         : id ("urn:uuid:" + make_uuid())
451         , label_text (label_text_)
452 {
453
454 }
455
456
457 void
458 ContentVersion::as_xml (xmlpp::Element* parent) const
459 {
460         auto cv = parent->add_child("ContentVersion");
461         cv->add_child("Id")->add_child_text(id);
462         cv->add_child("LabelText")->add_child_text(label_text);
463 }
464
465
466 Luminance::Luminance (cxml::ConstNodePtr node)
467         : _value(raw_convert<float>(node->content()))
468         , _unit(string_to_unit(node->string_attribute("units")))
469 {
470
471 }
472
473
474 Luminance::Luminance (float value, Unit unit)
475         : _unit (unit)
476 {
477         set_value (value);
478 }
479
480
481 void
482 Luminance::set_value (float v)
483 {
484         if (v < 0) {
485                 throw dcp::MiscError (String::compose("Invalid luminance value %1", v));
486         }
487
488         _value = v;
489 }
490
491
492 void
493 Luminance::as_xml (xmlpp::Element* parent, string ns) const
494 {
495         auto lum = parent->add_child("Luminance", ns);
496         lum->set_attribute("units", unit_to_string(_unit));
497         lum->add_child_text(raw_convert<string>(_value, 3));
498 }
499
500
501 string
502 Luminance::unit_to_string (Unit u)
503 {
504         switch (u) {
505         case Unit::CANDELA_PER_SQUARE_METRE:
506                 return "candela-per-square-metre";
507         case Unit::FOOT_LAMBERT:
508                 return "foot-lambert";
509         default:
510                 DCP_ASSERT (false);
511         }
512
513         return {};
514 }
515
516
517 Luminance::Unit
518 Luminance::string_to_unit (string u)
519 {
520         if (u == "candela-per-square-metre") {
521                 return Unit::CANDELA_PER_SQUARE_METRE;
522         } else if (u == "foot-lambert") {
523                 return Unit::FOOT_LAMBERT;
524         }
525
526         throw XMLError (String::compose("Invalid luminance unit %1", u));
527 }
528
529
530 float
531 Luminance::value_in_foot_lamberts () const
532 {
533         switch (_unit) {
534         case Unit::CANDELA_PER_SQUARE_METRE:
535                 return _value / 3.426;
536         case Unit::FOOT_LAMBERT:
537                 return _value;
538         default:
539                 DCP_ASSERT (false);
540         }
541 }
542
543
544 bool
545 dcp::operator== (Luminance const& a, Luminance const& b)
546 {
547         return fabs(a.value() - b.value()) < 0.001 && a.unit() == b.unit();
548 }
549
550
551 MainSoundConfiguration::MainSoundConfiguration (string s)
552 {
553         vector<string> parts;
554         boost::split (parts, s, boost::is_any_of("/"));
555         if (parts.size() != 2) {
556                 throw MainSoundConfigurationError (s);
557         }
558
559         if (parts[0] == "51") {
560                 _field = MCASoundField::FIVE_POINT_ONE;
561         } else if (parts[0] == "71") {
562                 _field = MCASoundField::SEVEN_POINT_ONE;
563         } else {
564                 throw MainSoundConfigurationError (s);
565         }
566
567         vector<string> channels;
568         boost::split (channels, parts[1], boost::is_any_of(","));
569
570         if (channels.size() > 16) {
571                 throw MainSoundConfigurationError (s);
572         }
573
574         for (auto i: channels) {
575                 if (i == "-") {
576                         _channels.push_back(optional<Channel>());
577                 } else {
578                         _channels.push_back(mca_id_to_channel(i));
579                 }
580         }
581 }
582
583
584 MainSoundConfiguration::MainSoundConfiguration (MCASoundField field, int channels)
585         : _field (field)
586 {
587         _channels.resize (channels);
588 }
589
590
591 string
592 MainSoundConfiguration::to_string () const
593 {
594         string c;
595         if (_field == MCASoundField::FIVE_POINT_ONE) {
596                 c = "51/";
597         } else {
598                 c = "71/";
599         }
600
601         for (auto i: _channels) {
602                 if (!i) {
603                         c += "-,";
604                 } else {
605                         c += channel_to_mca_id(*i, _field) + ",";
606                 }
607         }
608
609         if (c.length() > 0) {
610                 c = c.substr(0, c.length() - 1);
611         }
612
613         return c;
614 }
615
616
617 optional<Channel>
618 MainSoundConfiguration::mapping (int index) const
619 {
620         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
621         return _channels[index];
622 }
623
624
625 void
626 MainSoundConfiguration::set_mapping (int index, Channel c)
627 {
628         DCP_ASSERT (static_cast<size_t>(index) < _channels.size());
629         _channels[index] = c;
630 }
631
632
633 string
634 dcp::status_to_string (Status s)
635 {
636         switch (s) {
637         case Status::FINAL:
638                 return "final";
639         case Status::TEMP:
640                 return "temp";
641         case Status::PRE:
642                 return "pre";
643         default:
644                 DCP_ASSERT (false);
645         }
646 }
647
648
649 Status
650 dcp::string_to_status (string s)
651 {
652         if (s == "final") {
653                 return Status::FINAL;
654         } else if (s == "temp") {
655                 return Status::TEMP;
656         } else if (s == "pre") {
657                 return Status::PRE;
658         }
659
660         DCP_ASSERT (false);
661 }
662
663
664 Channel
665 dcp::mca_id_to_channel (string id)
666 {
667         if (id == "L") {
668                 return Channel::LEFT;
669         } else if (id == "R") {
670                 return Channel::RIGHT;
671         } else if (id == "C") {
672                 return Channel::CENTRE;
673         } else if (id == "LFE") {
674                 return Channel::LFE;
675         } else if (id == "Ls" || id == "Lss") {
676                 return Channel::LS;
677         } else if (id == "Rs" || id == "Rss") {
678                 return Channel::RS;
679         } else if (id == "HI") {
680                 return Channel::HI;
681         } else if (id == "VIN") {
682                 return Channel::VI;
683         } else if (id == "Lrs") {
684                 return Channel::BSL;
685         } else if (id == "Rrs") {
686                 return Channel::BSR;
687         } else if (id == "DBOX") {
688                 return Channel::MOTION_DATA;
689         } else if (id == "FSKSync") {
690                 return Channel::SYNC_SIGNAL;
691         } else if (id == "SLVS") {
692                 return Channel::SIGN_LANGUAGE;
693         }
694
695         throw UnknownChannelIdError (id);
696 }
697
698
699 string
700 dcp::channel_to_mca_id (Channel c, MCASoundField field)
701 {
702         switch (c) {
703         case Channel::LEFT:
704                 return "L";
705         case Channel::RIGHT:
706                 return "R";
707         case Channel::CENTRE:
708                 return "C";
709         case Channel::LFE:
710                 return "LFE";
711         case Channel::LS:
712                 return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
713         case Channel::RS:
714                 return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
715         case Channel::HI:
716                 return "HI";
717         case Channel::VI:
718                 return "VIN";
719         case Channel::BSL:
720                 return "Lrs";
721         case Channel::BSR:
722                 return "Rrs";
723         case Channel::MOTION_DATA:
724                 return "DBOX";
725         case Channel::SYNC_SIGNAL:
726                 return "FSKSync";
727         case Channel::SIGN_LANGUAGE:
728                 return "SLVS";
729         default:
730                 break;
731         }
732
733         DCP_ASSERT (false);
734 }
735
736
737 string
738 dcp::channel_to_mca_name (Channel c, MCASoundField field)
739 {
740         switch (c) {
741         case Channel::LEFT:
742                 return "Left";
743         case Channel::RIGHT:
744                 return "Right";
745         case Channel::CENTRE:
746                 return "Center";
747         case Channel::LFE:
748                 return "LFE";
749         case Channel::LS:
750                 return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
751         case Channel::RS:
752                 return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
753         case Channel::HI:
754                 return "Hearing Impaired";
755         case Channel::VI:
756                 return "Visually Impaired-Narrative";
757         case Channel::BSL:
758                 return "Left Rear Surround";
759         case Channel::BSR:
760                 return "Right Rear Surround";
761         case Channel::MOTION_DATA:
762                 return "D-BOX Motion Code Primary Stream";
763         case Channel::SYNC_SIGNAL:
764                 return "FSK Sync";
765         case Channel::SIGN_LANGUAGE:
766                 return "Sign Language Video Stream";
767         default:
768                 break;
769         }
770
771         DCP_ASSERT (false);
772 }
773
774
775 ASDCP::UL
776 dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dictionary const* dict)
777 {
778         static byte_t sync_signal[] = {
779                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x03, 0x02, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00
780         };
781
782         static byte_t sign_language[] = {
783                 0x06, 0x0e, 0x2b, 0x34, 0x04, 0x01, 0x01, 0x0d, 0x0d, 0x0f, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00
784         };
785
786         switch (c) {
787         case Channel::LEFT:
788                 return dict->ul(ASDCP::MDD_DCAudioChannel_L);
789         case Channel::RIGHT:
790                 return dict->ul(ASDCP::MDD_DCAudioChannel_R);
791         case Channel::CENTRE:
792                 return dict->ul(ASDCP::MDD_DCAudioChannel_C);
793         case Channel::LFE:
794                 return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
795         case Channel::LS:
796                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
797         case Channel::RS:
798                 return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
799         case Channel::HI:
800                 return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
801         case Channel::VI:
802                 return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
803         case Channel::BSL:
804                 return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
805         case Channel::BSR:
806                 return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
807         case Channel::MOTION_DATA:
808                 return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
809         case Channel::SYNC_SIGNAL:
810                 return ASDCP::UL(sync_signal);
811         case Channel::SIGN_LANGUAGE:
812                 return ASDCP::UL(sign_language);
813         default:
814                 break;
815         }
816
817         DCP_ASSERT (false);
818 }
819
820
821 vector<dcp::Channel>
822 dcp::used_audio_channels ()
823 {
824         return {
825                 Channel::LEFT,
826                 Channel::RIGHT,
827                 Channel::CENTRE,
828                 Channel::LFE,
829                 Channel::LS,
830                 Channel::RS,
831                 Channel::HI,
832                 Channel::VI,
833                 Channel::BSL,
834                 Channel::BSR,
835                 Channel::MOTION_DATA,
836                 Channel::SYNC_SIGNAL,
837                 Channel::SIGN_LANGUAGE
838         };
839 }
840
841
842 string
843 dcp::formulation_to_string (dcp::Formulation formulation)
844 {
845         switch (formulation) {
846         case Formulation::MODIFIED_TRANSITIONAL_1:
847                 return "modified-transitional-1";
848         case Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1:
849                 return "multiple-modified-transitional-1";
850         case Formulation::DCI_ANY:
851                 return "dci-any";
852         case Formulation::DCI_SPECIFIC:
853                 return "dci-specific";
854         }
855
856         DCP_ASSERT (false);
857 }
858
859
860 dcp::Formulation
861 dcp::string_to_formulation (string formulation)
862 {
863         if (formulation == "modified-transitional-1") {
864                 return Formulation::MODIFIED_TRANSITIONAL_1;
865         } else if (formulation == "multiple-modified-transitional-1") {
866                 return Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1;
867         } else if (formulation == "dci-any") {
868                 return Formulation::DCI_ANY;
869         } else if (formulation == "dci-specific") {
870                 return Formulation::DCI_SPECIFIC;
871         }
872
873         DCP_ASSERT (false);
874 }
875