2 #ifndef OPENGM_LAZYFLIPPER_HXX
3 #define OPENGM_LAZYFLIPPER_HXX
27 typedef std::vector<ValueType> tag_container_type;
28 typedef std::vector<size_t> index_container_type;
29 typedef index_container_type::const_iterator const_iterator;
31 Tagging(
const size_t = 0);
32 void append(
const size_t);
33 ValueType tag(
const size_t)
const;
34 void tag(
const size_t,
const typename Tagging<T>::ValueType);
36 const_iterator begin();
37 const_iterator begin()
const;
39 const_iterator end()
const;
42 tag_container_type tags_;
43 index_container_type indices_;
49 typedef std::set<size_t>::const_iterator const_iterator;
51 Adjacency(
const size_t = 0);
52 void resize(
const size_t);
53 void connect(
const size_t,
const size_t);
54 bool connected(
const size_t,
const size_t)
const;
55 const_iterator neighborsBegin(
const size_t);
56 const_iterator neighborsBegin(
const size_t)
const;
57 const_iterator neighborsEnd(
const size_t);
58 const_iterator neighborsEnd(
const size_t)
const;
61 std::vector<std::set<size_t> > neighbors_;
74 typedef size_t NodeIndex;
77 static const NodeIndex NONODE = -1;
82 NodeIndex levelAnchor(
const Level&);
83 NodeIndex push_back(
const Value&, NodeIndex);
84 size_t testInvariant();
85 std::string asString();
86 Value& value(NodeIndex);
87 Level level(NodeIndex);
88 NodeIndex parent(NodeIndex);
89 NodeIndex levelOrderSuccessor(NodeIndex);
90 size_t numberOfChildren(NodeIndex);
91 NodeIndex child(NodeIndex,
const size_t);
92 void setLevelOrderSuccessor(NodeIndex, NodeIndex);
96 Node(
const Value& value)
97 : value_(value), parent_(NONODE),
98 children_(std::vector<NodeIndex>()),
99 level_(0), levelOrderSuccessor_(NONODE)
103 std::vector<NodeIndex> children_;
105 NodeIndex levelOrderSuccessor_;
107 std::vector<Node> nodes_;
108 std::vector<NodeIndex> levelAnchors_;
117 template<
class GM,
class ACC = Minimizer>
125 static const SubgraphForestNode
NONODE = SubgraphForest::NONODE;
132 template<
class StateIterator>
135 StateIterator stateBegin,
136 StateIterator stateEnd,
160 template<
class StateIterator>
162 std::string
name()
const;
169 template<
class VisitorType>
176 template<
class VisitorType>
178 template<
class VisitorType>
182 SubgraphForestNode appendVariableToPath(SubgraphForestNode);
183 SubgraphForestNode generateFirstPathOfLength(
const size_t);
184 SubgraphForestNode generateNextPathOfSameLength(SubgraphForestNode);
185 void activateInfluencedVariables(SubgraphForestNode,
const size_t);
186 void deactivateAllVariables(
const size_t);
187 SubgraphForestNode firstActivePath(
const size_t);
188 SubgraphForestNode nextActivePath(SubgraphForestNode,
const size_t);
189 ValueType energyAfterFlip(SubgraphForestNode);
190 void flip(SubgraphForestNode);
191 const bool flipMultiLabel(SubgraphForestNode);
193 const GraphicalModelType& gm_;
194 Adjacency variableAdjacency_;
196 Tagging<bool> activation_[2];
197 SubgraphForest subgraphForest_;
198 size_t maxSubgraphSize_;
199 Tribool useMultilabelInference_;
205 inline Tagging<T>::Tagging(
208 : tags_(tag_container_type(size)),
209 indices_(index_container_type())
213 inline void Tagging<T>::append(
217 tags_.resize(tags_.size() + number);
222 inline typename Tagging<T>::ValueType
236 const typename Tagging<T>::ValueType tag
241 if(tags_[index] == T()) {
242 indices_.push_back(index);
254 for(const_iterator it = indices_.begin(); it != indices_.end(); ++it) {
261 inline typename Tagging<T>::const_iterator
262 Tagging<T>::begin()
const
264 return indices_.begin();
268 inline typename Tagging<T>::const_iterator
269 Tagging<T>::end()
const
271 return indices_.end();
275 inline typename Tagging<T>::const_iterator
278 return indices_.begin();
282 inline typename Tagging<T>::const_iterator
285 return indices_.end();
290 Adjacency::Adjacency(
301 neighbors_.resize(size);
311 neighbors_[j].insert(k);
312 neighbors_[k].insert(j);
316 Adjacency::connected(
321 if(neighbors_[j].size() < neighbors_[k].size()) {
322 if(neighbors_[j].find(k) == neighbors_[j].end()) {
330 if(neighbors_[k].find(j) == neighbors_[k].end()) {
339 inline Adjacency::const_iterator
340 Adjacency::neighborsBegin(
344 return neighbors_[index].begin();
347 inline Adjacency::const_iterator
348 Adjacency::neighborsBegin(
352 return neighbors_[index].begin();
355 inline Adjacency::const_iterator
356 Adjacency::neighborsEnd(
360 return neighbors_[index].end();
363 inline Adjacency::const_iterator
364 Adjacency::neighborsEnd(
368 return neighbors_[index].end();
374 inline Forest<T>::Forest()
375 : nodes_(
std::vector<typename Forest<T>::Node>()),
376 levelAnchors_(
std::vector<typename Forest<T>::NodeIndex>())
383 return levelAnchors_.size();
390 return nodes_.size();
394 inline typename Forest<T>::NodeIndex
395 Forest<T>::levelAnchor(
396 const typename Forest<T>::Level& level
400 return levelAnchors_[level];
404 inline typename Forest<T>::Value&
406 typename Forest<T>::NodeIndex n
410 return nodes_[n].value_;
414 inline typename Forest<T>::Level
416 typename Forest<T>::NodeIndex n
420 return nodes_[n].level_;
424 inline typename Forest<T>::NodeIndex
426 typename Forest<T>::NodeIndex n
430 return nodes_[n].parent_;
434 inline typename Forest<T>::NodeIndex
435 Forest<T>::levelOrderSuccessor(
436 typename Forest<T>::NodeIndex n
440 return nodes_[n].levelOrderSuccessor_;
445 Forest<T>::numberOfChildren(
446 typename Forest<T>::NodeIndex n
450 return nodes_[n].children_.size();
454 inline typename Forest<T>::NodeIndex
456 typename Forest<T>::NodeIndex n,
460 OPENGM_ASSERT((n<nodes_.size() && j<nodes_[n].children_.size()));
461 return nodes_[n].children_[j];
465 typename Forest<T>::NodeIndex
466 Forest<T>::push_back(
468 typename Forest<T>::NodeIndex parentNodeIndex
473 NodeIndex nodeIndex = nodes_.size();
476 nodes_.push_back(node);
480 if(parentNodeIndex !=
NONODE) {
481 nodes_[nodeIndex].parent_ = parentNodeIndex;
482 nodes_[parentNodeIndex].children_.push_back(nodeIndex);
483 nodes_[nodeIndex].level_ = nodes_[parentNodeIndex].level_ + 1;
485 if(nodes_[nodeIndex].level_ >= levelAnchors_.size()) {
486 OPENGM_ASSERT(levelAnchors_.size() == nodes_[nodeIndex].level_);
487 levelAnchors_.push_back(nodeIndex);
495 Forest<T>::testInvariant()
497 if(nodes_.size() == 0) {
505 size_t numberOfRoots = 0;
506 size_t nodesVisited = 0;
508 NodeIndex p = levelAnchors_[0];
522 for(
size_t j=0; j<nodes_[parent(p)].children_.size(); ++j) {
523 if(nodes_[parent(p)].children_[j] == p) {
531 if(levelOrderSuccessor(p) !=
NONODE) {
532 p = levelOrderSuccessor(p);
535 if(level+1 < levelAnchors_.size()) {
538 p = levelAnchors_[level];
548 return numberOfRoots;
554 Forest<T>::asString()
556 std::ostringstream out(std::ostringstream::out);
557 for(
size_t level=0; level<levels(); ++level) {
558 NodeIndex p = levelAnchor(level);
564 out <<
value(q)+1 <<
' ';
569 p = levelOrderSuccessor(p);
577 Forest<T>::setLevelOrderSuccessor(
578 typename Forest<T>::NodeIndex nodeIndex,
579 typename Forest<T>::NodeIndex successorNodeIndex
582 OPENGM_ASSERT((nodeIndex < nodes_.size() && successorNodeIndex < nodes_.size()));
583 nodes_[nodeIndex].levelOrderSuccessor_ = successorNodeIndex;
588 template<
class GM,
class ACC>
593 const Tribool useMultilabelInference
596 variableAdjacency_(Adjacency(gm.numberOfVariables())),
599 maxSubgraphSize_(maxSubgraphSize),
600 useMultilabelInference_(useMultilabelInference)
602 if(gm_.numberOfVariables() == 0) {
603 throw RuntimeError(
"The graphical model has no variables.");
607 activation_[0].append(gm_.numberOfVariables());
608 activation_[1].append(gm_.numberOfVariables());
610 for(
size_t j=0; j<gm_.numberOfFactors(); ++j) {
612 for(
size_t m=0; m<factor.numberOfVariables(); ++m) {
613 for(
size_t n=m+1; n<factor.numberOfVariables(); ++n) {
614 variableAdjacency_.connect(factor.variableIndex(m), factor.variableIndex(n));
620 template<
class GM,
class ACC>
627 variableAdjacency_(Adjacency(gm.numberOfVariables())),
630 maxSubgraphSize_(param.maxSubgraphSize_),
631 useMultilabelInference_(param.inferMultilabel_)
633 if(gm_.numberOfVariables() == 0) {
634 throw RuntimeError(
"The graphical model has no variables.");
638 activation_[0].append(gm_.numberOfVariables());
639 activation_[1].append(gm_.numberOfVariables());
641 for(
size_t j=0; j<gm_.numberOfFactors(); ++j) {
643 for(
size_t m=0; m<factor.numberOfVariables(); ++m) {
644 for(
size_t n=m+1; n<factor.numberOfVariables(); ++n) {
645 variableAdjacency_.connect(factor.variableIndex(m), factor.variableIndex(n));
654 template<
class GM,
class ACC>
660 template<
class GM,
class ACC>
661 template<
class StateIterator>
665 const size_t maxSubgraphSize,
667 const Tribool useMultilabelInference
670 variableAdjacency_(Adjacency(gm_.numberOfVariables())),
674 useMultilabelInference_(useMultilabelInference)
676 if(gm_.numberOfVariables() == 0) {
677 throw RuntimeError(
"The graphical model has no variables.");
681 activation_[0].append(gm_.numberOfVariables());
682 activation_[1].append(gm_.numberOfVariables());
684 for(
size_t j=0; j<gm_.numberOfFactors(); ++j) {
686 for(
size_t m=0; m<factor.numberOfVariables(); ++m) {
687 for(
size_t n=m+1; n<factor.numberOfVariables(); ++n) {
688 variableAdjacency_.connect(factor.variableIndex(m), factor.variableIndex(n));
694 template<
class GM,
class ACC>
699 movemaker_.initialize(begin);
702 template<
class GM,
class ACC>
706 return "LazyFlipper";
709 template<
class GM,
class ACC>
716 template<
class GM,
class ACC>
720 return maxSubgraphSize_;
723 template<
class GM,
class ACC>
726 const size_t maxSubgraphSize
729 if(maxSubgraphSize < 1) {
733 maxSubgraphSize_ = maxSubgraphSize;
738 template<
class GM,
class ACC>
739 template<
class VisitorType>
746 if(this->useMultilabelInference_ ==
true) {
749 else if(this->useMultilabelInference_ ==
false) {
754 for(
size_t i=0; i<gm_.numberOfVariables(); ++i) {
755 if(gm_.numberOfLabels(i) != 2) {
763 return this->inferMultiLabel(visitor);
766 return this->inferBinaryLabel(visitor);
771 template<
class GM,
class ACC>
776 return this->infer(visitor);
779 template<
class GM,
class ACC>
780 template<
class VisitorType>
786 bool continueInf =
true;
790 visitor.begin(*
this);
793 if(visitor(*
this)!=0){
797 SubgraphForestNode p = generateFirstPathOfLength(length);
803 if(AccumulationType::bop(energyAfterFlip(p), movemaker_.value())) {
805 activateInfluencedVariables(p, 0);
807 if(visitor(*
this)!=0){
812 p = generateNextPathOfSameLength(p);
814 size_t currentActivationList = 0;
815 size_t nextActivationList = 1;
817 SubgraphForestNode p2 = firstActivePath(currentActivationList);
822 while(p2 != NONODE) {
823 if(AccumulationType::bop(energyAfterFlip(p2), movemaker_.value())) {
825 activateInfluencedVariables(p2, nextActivationList);
827 if(visitor(*
this)!=0){
832 p2 = nextActivePath(p2, currentActivationList);
834 deactivateAllVariables(currentActivationList);
835 nextActivationList = 1 - nextActivationList;
836 currentActivationList = 1 - currentActivationList;
840 if(length == maxSubgraphSize_) {
849 subgraphForest_.testInvariant();
858 template<
class GM,
class ACC>
860 LazyFlipper<GM, ACC>::inferBinaryLabel()
866 template<
class GM,
class ACC>
867 template<
class VisitorType>
869 LazyFlipper<GM, ACC>::inferMultiLabel(
873 bool continueInf =
true;
877 visitor.begin(*
this);
880 if(visitor(*
this)!=0){
884 SubgraphForestNode p = generateFirstPathOfLength(length);
890 bool flipped = flipMultiLabel(p);
892 activateInfluencedVariables(p, 0);
894 if(visitor(*
this)!=0){
899 p = generateNextPathOfSameLength(p);
901 size_t currentActivationList = 0;
902 size_t nextActivationList = 1;
904 SubgraphForestNode p2 = firstActivePath(currentActivationList);
909 while(p2 != NONODE) {
910 bool flipped = flipMultiLabel(p2);
912 activateInfluencedVariables(p2, nextActivationList);
914 if(visitor(*
this)!=0){
919 p2 = nextActivePath(p2, currentActivationList);
921 deactivateAllVariables(currentActivationList);
922 nextActivationList = 1 - nextActivationList;
923 currentActivationList = 1 - currentActivationList;
927 if(length == maxSubgraphSize_) {
936 subgraphForest_.testInvariant();
945 template<
class GM,
class ACC>
947 LazyFlipper<GM, ACC>::inferMultiLabel()
949 EmptyVisitorType visitor;
950 return this->inferMultiLabel(visitor);
953 template<
class GM,
class ACC>
956 std::vector<LabelType>& arg,
964 arg.resize(gm_.numberOfVariables());
965 for(
size_t j=0; j<gm_.numberOfVariables(); ++j) {
966 arg[j] = movemaker_.state(j);
972 template<
class GM,
class ACC>
976 return movemaker_.value();
981 template<
class GM,
class ACC>
988 std::vector<size_t> variableIndicesOnPath(subgraphForest_.level(p) + 1);
990 SubgraphForestNode p2 = p;
991 for(
size_t j=0; j<=subgraphForest_.level(p); ++j) {
993 variableIndicesOnPath[subgraphForest_.level(p) - j] = subgraphForest_.value(p2);
994 p2 = subgraphForest_.parent(p2);
999 size_t minVI = variableIndicesOnPath[0];
1000 size_t maxVI = variableIndicesOnPath[0];
1001 for(
size_t j=1; j<variableIndicesOnPath.size(); ++j) {
1002 if(variableIndicesOnPath[j] > maxVI) {
1003 maxVI = variableIndicesOnPath[j];
1008 if(subgraphForest_.numberOfChildren(p) > 0) {
1009 size_t maxChildIndex = subgraphForest_.numberOfChildren(p) - 1;
1010 minVI = subgraphForest_.value(subgraphForest_.child(p, maxChildIndex));
1013 std::set<size_t> candidateVariableIndices;
1015 SubgraphForestNode q = p;
1016 while(q != NONODE) {
1017 for(Adjacency::const_iterator it = variableAdjacency_.neighborsBegin(subgraphForest_.value(q));
1018 it != variableAdjacency_.neighborsEnd(subgraphForest_.value(q)); ++it) {
1019 candidateVariableIndices.insert(*it);
1021 q = subgraphForest_.parent(q);
1025 for(std::set<size_t>::const_iterator it = candidateVariableIndices.begin();
1026 it != candidateVariableIndices.end(); ++it) {
1028 if(*it > minVI && std::find(variableIndicesOnPath.begin(), variableIndicesOnPath.end(), *it) == variableIndicesOnPath.end()) {
1035 return subgraphForest_.push_back(*it, p);
1039 for(
size_t j=1; j<variableIndicesOnPath.size(); ++j) {
1040 if(variableAdjacency_.connected(variableIndicesOnPath[j-1], *it)) {
1043 for(
size_t k=j; k<variableIndicesOnPath.size(); ++k) {
1044 if(*it < variableIndicesOnPath[k]) {
1054 return subgraphForest_.push_back(*it, p);
1063 template<
class GM,
class ACC>
1064 typename LazyFlipper<GM, ACC>::SubgraphForestNode
1065 LazyFlipper<GM, ACC>::generateFirstPathOfLength(
1070 if(length > gm_.numberOfVariables()) {
1075 SubgraphForestNode p = subgraphForest_.push_back(0, NONODE);
1080 SubgraphForestNode p = subgraphForest_.levelAnchor(length-2);
1081 while(p != NONODE) {
1082 SubgraphForestNode p2 = appendVariableToPath(p);
1087 p = subgraphForest_.levelOrderSuccessor(p);
1095 template<
class GM,
class ACC>
1096 typename LazyFlipper<GM, ACC>::SubgraphForestNode
1097 LazyFlipper<GM, ACC>::generateNextPathOfSameLength(
1098 SubgraphForestNode predecessor
1101 if(subgraphForest_.level(predecessor) == 0) {
1102 if(subgraphForest_.value(predecessor) + 1 < gm_.numberOfVariables()) {
1103 SubgraphForestNode newNode =
1104 subgraphForest_.push_back(subgraphForest_.value(predecessor) + 1, NONODE);
1105 subgraphForest_.setLevelOrderSuccessor(predecessor, newNode);
1114 for(SubgraphForestNode parent = subgraphForest_.parent(predecessor);
1115 parent != NONODE; parent = subgraphForest_.levelOrderSuccessor(parent) ) {
1116 SubgraphForestNode newNode = appendVariableToPath(parent);
1117 if(newNode != NONODE) {
1119 subgraphForest_.setLevelOrderSuccessor(predecessor, newNode);
1127 template<
class GM,
class ACC>
1129 LazyFlipper<GM, ACC>::activateInfluencedVariables(
1130 SubgraphForestNode p,
1131 const size_t activationListIndex
1135 while(p != NONODE) {
1136 activation_[activationListIndex].tag(subgraphForest_.value(p),
true);
1137 for(Adjacency::const_iterator it = variableAdjacency_.neighborsBegin(subgraphForest_.value(p));
1138 it != variableAdjacency_.neighborsEnd(subgraphForest_.value(p)); ++it) {
1139 activation_[activationListIndex].tag(*it,
true);
1141 p = subgraphForest_.parent(p);
1145 template<
class GM,
class ACC>
1147 LazyFlipper<GM, ACC>::deactivateAllVariables(
1148 const size_t activationListIndex
1152 activation_[activationListIndex].untag();
1155 template<
class GM,
class ACC>
1156 typename LazyFlipper<GM, ACC>::SubgraphForestNode
1157 LazyFlipper<GM, ACC>::firstActivePath(
1158 const size_t activationListIndex
1161 if(subgraphForest_.levels() == 0) {
1166 SubgraphForestNode p = subgraphForest_.levelAnchor(0);
1167 while(p != NONODE) {
1168 if(activation_[activationListIndex].tag(subgraphForest_.value(p))) {
1171 p = subgraphForest_.levelOrderSuccessor(p);
1180 template<
class GM,
class ACC>
1181 typename LazyFlipper<GM, ACC>::SubgraphForestNode
1182 LazyFlipper<GM, ACC>::nextActivePath(
1183 SubgraphForestNode predecessor,
1184 const size_t activationListIndex
1188 if(subgraphForest_.levelOrderSuccessor(predecessor) == NONODE) {
1189 if(subgraphForest_.level(predecessor) + 1 < subgraphForest_.levels()) {
1191 predecessor = subgraphForest_.levelAnchor(subgraphForest_.level(predecessor) + 1);
1200 predecessor = subgraphForest_.levelOrderSuccessor(predecessor);
1202 SubgraphForestNode p = predecessor;
1203 while(p != NONODE) {
1205 if(activation_[activationListIndex].tag(subgraphForest_.value(p))) {
1208 p = subgraphForest_.parent(p);
1213 template<
class GM,
class ACC>
1214 inline typename LazyFlipper<GM, ACC>::ValueType
1215 LazyFlipper<GM, ACC>::energyAfterFlip(
1216 SubgraphForestNode node
1219 size_t numberOfFlippedVariables = subgraphForest_.level(node) + 1;
1220 std::vector<size_t> flippedVariableIndices(numberOfFlippedVariables);
1221 std::vector<LabelType> flippedVariableStates(numberOfFlippedVariables);
1222 for(
size_t j=0; j<numberOfFlippedVariables; ++j) {
1224 flippedVariableIndices[j] = subgraphForest_.value(node);
1226 flippedVariableStates[j] = 1 - movemaker_.state(subgraphForest_.value(node));
1227 node = subgraphForest_.parent(node);
1230 return movemaker_.valueAfterMove(flippedVariableIndices.begin(),
1231 flippedVariableIndices.end(), flippedVariableStates.begin());
1235 template<
class GM,
class ACC>
1237 LazyFlipper<GM, ACC>::flip(
1238 SubgraphForestNode node
1241 size_t numberOfFlippedVariables = subgraphForest_.level(node) + 1;
1242 std::vector<size_t> flippedVariableIndices(numberOfFlippedVariables);
1243 std::vector<LabelType> flippedVariableStates(numberOfFlippedVariables);
1244 for(
size_t j=0; j<numberOfFlippedVariables; ++j) {
1246 flippedVariableIndices[j] = subgraphForest_.value(node);
1248 flippedVariableStates[j] = 1 - movemaker_.state(subgraphForest_.value(node));
1249 node = subgraphForest_.parent(node);
1252 movemaker_.move(flippedVariableIndices.begin(),
1253 flippedVariableIndices.end(), flippedVariableStates.begin());
1256 template<class GM, class ACC>
1258 LazyFlipper<GM, ACC>::flipMultiLabel(
1259 SubgraphForestNode node
1262 size_t numberOfVariables = subgraphForest_.level(node) + 1;
1263 std::vector<size_t> variableIndices(numberOfVariables);
1264 for(
size_t j=0; j<numberOfVariables; ++j) {
1266 variableIndices[j] = subgraphForest_.value(node);
1267 node = subgraphForest_.parent(node);
1270 ValueType energy = movemaker_.value();
1271 movemaker_.template moveOptimallyWithAllLabelsChanging<AccumulationType>(variableIndices.begin(), variableIndices.end());
1272 if(AccumulationType::bop(movemaker_.value(), energy)) {
1282 #endif // #ifndef OPENGM_LAZYFLIPPER_HXX
const GraphicalModelType & graphicalModel() const
const size_t maxSubgraphSize() const
Parameter(const size_t maxSubgraphSize, StateIterator stateBegin, StateIterator stateEnd, const Tribool inferMultilabel=Tribool::Maybe)
void setMaxSubgraphSize(const size_t)
visitors::TimingVisitor< LazyFlipper< GM, ACC > > TimingVisitorType
void setStartingPoint(typename std::vector< LabelType >::const_iterator)
set initial labeling
#define OPENGM_ASSERT(expression)
LazyFlipper(const GraphicalModelType &, const size_t=2, const Tribool useMultilabelInference=Tribool::Maybe)
void initialize(StateIterator)
GraphicalModelType::FactorType FactorType
std::vector< LabelType > startingPoint_
visitors::EmptyVisitor< LazyFlipper< GM, ACC > > EmptyVisitorType
GraphicalModelType::ValueType ValueType
Inference algorithm interface.
Parameter(const size_t maxSubgraphSize=2, const Tribool inferMultilabel=Tribool::Maybe)
size_t SubgraphForestNode
Forest< IndexType > SubgraphForest
Variable with three values (true=1, false=0, maybe=-1)
static const SubgraphForestNode NONODE
visitors::VerboseVisitor< LazyFlipper< GM, ACC > > VerboseVisitorType
InferenceTermination arg(std::vector< LabelType > &, const size_t=1) const
output a solution
GraphicalModelType::LabelType LabelType
A generalization of ICM B. Andres, J. H. Kappes, U. Koethe and Hamprecht F. A., The Lazy Flipper: MA...
InferenceTermination infer()
start the algorithm
ValueType value() const
return the solution (value)