308 LOG(warning) <<
" ! constraints file in not provided, proceeding without constraints";
311 LOG(info) <<
" " << jsonPath <<
" : constraints file provided";
312 std::ifstream ifs(jsonPath);
314 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: failed to open JSON file " << jsonPath;
317 nlohmann::json jsonData;
320 }
catch (
const std::exception& e) {
321 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: failed to parse JSON file " << jsonPath;
322 LOG(error) << e.what();
325 if (!jsonData.is_array()) {
326 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: top-level JSON is not an array in " << jsonPath;
329 const Int_t entriesCnt{
static_cast<Int_t>(jsonData.size())};
330 if (entriesCnt <= 0) {
331 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: empty constraints array in " << jsonPath;
335 auto detIdxByID = [&hierarchy](
Int_t encID) ->
Int_t {
338 }
catch (
const std::out_of_range&) {
342 auto isCoefKey = [](
const std::string& k) ->
Bool_t {
343 return k ==
"ParamName" || k ==
"ParamIndex" || k ==
"DetectorID" || k ==
"NodeName" || k ==
"ConstrIndex"
344 || k ==
"Value" || k ==
"RHS";
346 auto isTypedKey = [](
const std::string& k) ->
Bool_t {
347 return k ==
"Type" || k ==
"ParamName" || k ==
"Over" || k ==
"NodeMatch" || k ==
"ID" || k ==
"RHS"
348 || k ==
"Comment" || k ==
"IDComment";
353 std::vector<TVectorD> rows;
358 rowsRhs.push_back(rhs);
362 if (std::fabs(rhs) <= SOFT_ZERO)
365 LOG(warning) <<
" ! constraint #" << entry
366 <<
" has non-zero RHS -- (unlikely) violation is possible"
367 " (see comment in BmnAligner::SolveGlobal)";
370 for (
Int_t i = 0;
i < entriesCnt;
i++) {
371 const auto& jsConstr = jsonData[
i];
372 if (!jsConstr.is_object()) {
373 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
" is not an object";
379 if (jsConstr.contains(
"Type")) {
380 Int_t entryErrors{0};
381 auto fail = [&](
const std::string& what) {
382 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
" : " << what;
385 for (
auto it = jsConstr.begin(); it != jsConstr.end(); ++it)
386 if (!isTypedKey(it.key()))
387 fail(
"unexpected attribute '" + it.key() +
"'");
388 const nlohmann::json& jsType{jsConstr.at(
"Type")};
389 const std::string type{jsType.is_string() ? jsType.get<std::string>() :
""};
390 const Bool_t fix{type ==
"Fix"}, sum{type ==
"Sum"}, shear{type ==
"Shear"}, equal{type ==
"NodeEqual"};
391 if (!fix && !sum && !shear && !equal)
392 fail(
"unknown Type " + jsType.dump() +
", expected Fix / Sum / Shear / NodeEqual");
394 if (!jsConstr.contains(
"ParamName"))
395 fail(
"ParamName is required");
396 else if (
const std::string what{ParamList(jsConstr.at(
"ParamName"), params)}; !what.empty())
398 const Bool_t hasOver{jsConstr.contains(
"Over")}, hasMatch{jsConstr.contains(
"NodeMatch")},
399 hasID{jsConstr.contains(
"ID")}, hasRHS{jsConstr.contains(
"RHS")};
400 if (hasOver + hasMatch + hasID != 1)
401 fail(
"exactly one of Over / NodeMatch / ID required");
403 fail(
"ID is for NodeEqual only");
404 if (hasOver && equal)
405 fail(
"NodeEqual takes NodeMatch or ID, not Over");
406 if (hasMatch && shear)
407 fail(
"Shear weighs elements by their z, so it takes Over, not NodeMatch");
409 fail(
"NodeEqual takes no RHS");
410 if (hasRHS && !jsConstr.at(
"RHS").is_number())
411 fail(
"RHS " + jsConstr.at(
"RHS").dump() +
" is not a number");
413 errors += entryErrors;
419 std::vector<NodeList_t> groups(1);
422 what = LeafScope(jsConstr.at(
"Over"), hierarchy, groups[0]);
424 what = MatchComposites(jsConstr.at(
"NodeMatch"), hierarchy, groups[0]);
425 else if (
const nlohmann::json & ids{jsConstr.at(
"ID")}; !ids.is_array())
426 what =
"ID " + ids.dump() +
" is not an array";
428 for (
const nlohmann::json&
id : ids) {
429 const Int_t detIdx{
id.is_number_integer() ? detIdxByID(
id.get<Int_t>()) : -1};
431 what =
"unknown DetectorID " +
id.dump();
432 if (
id.is_number_integer())
436 if (std::find(groups[0].begin(), groups[0].end(), &hierarchy.
Layout().
ByNode()[detIdx])
439 what =
"ID lists " +
id.dump() +
" twice";
442 groups[0].push_back(&hierarchy.
Layout().
ByNode()[detIdx]);
444 if (what.empty() && shear
446 what =
"Shear needs the element z, but the detector model has no geometry";
449 errors += entryErrors;
453 if (equal && hasMatch) {
454 const NodeList_t composites{groups[0]};
456 for (
const NodeRef* comp : composites) {
459 if (node.IsComposite())
461 for (
Int_t n = hierarchy.
Nodes()[node.leaf].parent; n >= 0; n = hierarchy.
Nodes()[n].parent)
462 if (hierarchy.
Nodes()[n].name == comp->name) {
463 group.push_back(&node);
467 LOG_IF(warning,
group.size() < 2) <<
" ! constraint #" <<
i + 1 <<
" : composite '" << comp->name
468 <<
"' holds a single element, skipped";
469 if (
group.size() >= 2)
470 groups.push_back(
group);
472 }
else if (equal && groups[0].size() < 2) {
473 LOG(warning) <<
" ! constraint #" <<
i + 1 <<
" : the ID list names a single element, skipped";
478 if (shear && !groups[0].empty()) {
481 for (
const NodeRef* node : groups[0])
482 zMean += geometry[node->leaf].z;
483 zMean /= groups[0].size();
484 for (
const NodeRef* node : groups[0])
485 spread = std::max(spread, std::fabs(geometry[node->leaf].z - zMean));
486 if (!(spread > 0.0)) {
487 fail(
"Shear over elements which all lie at one z");
488 errors += entryErrors;
491 for (
const NodeRef* node : groups[0])
492 weight.push_back((geometry[node->leaf].z - zMean) / spread);
495 const std::size_t firstRow{rows.size()};
496 for (
const Int_t p : params) {
498 for (
const NodeRef* node : groups[0]) {
500 row(node->params[p].full) = 1.0;
503 if ((sum || shear) && !groups[0].empty()) {
505 for (std::size_t k = 0; k < groups[0].size(); k++)
506 row(groups[0][k]->params[p].full) = shear ? weight[k] : 1.0;
510 for (
const NodeList_t&
group : groups)
511 for (std::size_t k = 1; k <
group.size(); k++) {
513 row(
group[0]->params[p].full) = -1.0;
514 row(
group[k]->params[p].full) = 1.0;
518 if (rows.size() == firstRow) {
519 LOG(warning) <<
" ! constraint #" <<
i + 1 <<
" (" << type
520 <<
") addresses nothing in this model, no rows made";
524 LOG(info) <<
Form(
" constraint #%d (%s) : %zu row(s), rows #%zu..#%zu",
i + 1, type.c_str(),
525 rows.size() - firstRow, firstRow + 1, rows.size());
530 const nlohmann::json* jsCoeffs{
nullptr};
531 for (
auto it = jsConstr.begin(); it != jsConstr.end(); ++it) {
532 if (it.value().is_string() || it.key() ==
"IDComment")
534 if (it.key() ==
"Coefficients" && it.value().is_array())
535 jsCoeffs = &it.value();
537 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1
538 <<
" has unexpected attribute '" << it.key() <<
"'";
543 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1
544 <<
" has no 'Coefficients' array";
561 for (
Int_t cIdx = 0; cIdx < (
Int_t)jsCoeffs->size(); cIdx++) {
562 const auto& jsCoef = (*jsCoeffs)[cIdx];
563 if (!jsCoef.is_object()) {
564 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
", coefficient #"
565 << cIdx + 1 <<
" is not an object";
571 for (
auto it = jsCoef.begin(); it != jsCoef.end(); ++it)
572 if (!isCoefKey(it.key())) {
573 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
", coefficient #"
574 << cIdx + 1 <<
" has unknown attribute '" << it.key() <<
"'";
582 const Bool_t hasParamName{jsCoef.contains(
"ParamName")};
583 const Bool_t hasParamIndex{jsCoef.contains(
"ParamIndex")};
584 const Bool_t hasDetectorID{jsCoef.contains(
"DetectorID")};
585 const Bool_t hasNodeName{jsCoef.contains(
"NodeName")};
586 const Bool_t hasConstrIndex{jsCoef.contains(
"ConstrIndex")};
587 const Bool_t hasValue{jsCoef.contains(
"Value")};
588 const Bool_t hasRHSKey{jsCoef.contains(
"RHS")};
592 if (hasParamName || hasParamIndex || hasDetectorID || hasNodeName || hasConstrIndex || hasValue) {
593 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
", coefficient #"
594 << cIdx + 1 <<
" has RHS together with other attributes";
599 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1
600 <<
" has multiple RHS entries";
604 rowRhs = jsCoef[
"RHS"].get<
Double_t>();
605 noteRhs(rowRhs,
i + 1);
612 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
", coefficient #"
613 << cIdx + 1 <<
" has neither Value nor RHS";
619 if (hasConstrIndex) {
620 if (hasParamName || hasParamIndex || hasDetectorID || hasNodeName) {
621 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
", coefficient #"
622 << cIdx + 1 <<
" mixes ConstrIndex with locator attributes";
626 if (scheme == Locator) {
627 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1
628 <<
" mixes locator and direct schemes";
633 const Int_t idx{jsCoef[
"ConstrIndex"].get<
Int_t>()};
634 if (idx < 0 || idx >= hierarchy.
AllParams()) {
635 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
", coefficient #"
636 << cIdx + 1 <<
" : ConstrIndex " << idx <<
" out of range [0, " << hierarchy.
AllParams()
647 if (hasParamName == hasParamIndex) {
648 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
", coefficient #"
649 << cIdx + 1 <<
" : exactly one of ParamName / ParamIndex required";
653 if (hasDetectorID + hasNodeName != 1) {
654 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
", coefficient #"
655 << cIdx + 1 <<
" : exactly one of DetectorID / NodeName required";
659 if (scheme == Direct) {
660 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1
661 <<
" mixes locator and direct schemes";
669 const std::string name{jsCoef[
"ParamName"].get<std::string>()};
672 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
", coefficient #"
673 << cIdx + 1 <<
" : unknown ParamName '" << name <<
"'";
678 paramIdx = jsCoef[
"ParamIndex"].get<
Int_t>();
680 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
", coefficient #"
681 << cIdx + 1 <<
" : ParamIndex " << paramIdx <<
" out of range [0, "
689 const NodeRef* target{
nullptr};
691 const std::string name{jsCoef[
"NodeName"].get<std::string>()};
694 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
", coefficient #"
695 << cIdx + 1 <<
" : unknown NodeName '" << name <<
"'";
700 const Int_t encID{jsCoef[
"DetectorID"].get<
Int_t>()};
701 const Int_t detIdx{detIdxByID(encID)};
703 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: constraint #" <<
i + 1 <<
", coefficient #"
704 << cIdx + 1 <<
" : unknown DetectorID " << encID <<
" ("
711 row(target->
params[paramIdx].full) = value;
715 LOG(warning) <<
" ! constraint #" <<
i + 1 <<
" has all-zero LHS - it is either trivial or unsatisfiable";
721 LOG(error) <<
" <!> BmnAlignConfig::LoadConstraints: " << errors <<
" error(s) detected, aborting";
724 const Int_t rowsCnt{
static_cast<Int_t>(rows.size())};
727 for (
Int_t r = 0; r < rowsCnt; r++) {
729 priors.
rhs(r) = rowsRhs[r];
731 LOG(info) <<
" " << rowsCnt <<
" constraint row(s) loaded from " << entriesCnt <<
" entries";
776 LOG(info) <<
" presigmas file is not provided, proceeding without priors";
779 LOG(info) <<
" " << jsonPath <<
" : presigmas file provided";
780 std::ifstream ifs(jsonPath);
782 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: failed to open JSON file " << jsonPath;
785 nlohmann::json jsonData;
788 }
catch (
const std::exception& e) {
789 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: failed to parse JSON file " << jsonPath;
790 LOG(error) << e.what();
793 if (!jsonData.is_array()) {
794 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: top-level JSON is not an array in " << jsonPath;
797 const Int_t entriesCnt = jsonData.size();
798 if (entriesCnt <= 0) {
799 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: empty presigmas array in " << jsonPath;
803 auto detIdxByID = [&hierarchy](
Int_t encID) ->
Int_t {
806 }
catch (
const std::out_of_range&) {
810 auto isPresigmaKey = [](
const std::string& k) ->
Bool_t {
811 return k ==
"ParamName" || k ==
"ParamIndex" || k ==
"DetectorID" || k ==
"NodeName" || k ==
"Over"
812 || k ==
"NodeMatch" || k ==
"Sigma" || k ==
"Ref" || k ==
"IDComment";
819 for (
Int_t i = 0;
i < entriesCnt;
i++) {
820 const auto& jsEntry = jsonData[
i];
821 if (!jsEntry.is_object()) {
822 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: presigma #" <<
i + 1 <<
" is not an object";
828 for (
auto it = jsEntry.begin(); it != jsEntry.end(); ++it) {
829 if (it.value().is_string())
831 if (!isPresigmaKey(it.key())) {
832 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: presigma #" <<
i + 1 <<
" has unknown attribute '"
842 const Bool_t hasParamName{jsEntry.contains(
"ParamName")};
843 const Bool_t hasParamIndex{jsEntry.contains(
"ParamIndex")};
844 const Bool_t hasDetectorID{jsEntry.contains(
"DetectorID")};
845 const Bool_t hasNodeName{jsEntry.contains(
"NodeName")};
846 const Bool_t hasOver{jsEntry.contains(
"Over")};
847 const Bool_t hasMatch{jsEntry.contains(
"NodeMatch")};
849 if (hasParamName == hasParamIndex) {
850 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: presigma #" <<
i + 1
851 <<
" : exactly one of ParamName / ParamIndex required";
855 if (hasDetectorID + hasNodeName + hasOver + hasMatch > 1) {
856 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: presigma #" <<
i + 1
857 <<
" : at most one of DetectorID / NodeName / Over / NodeMatch allowed";
861 if (!jsEntry.contains(
"Sigma")) {
862 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: presigma #" <<
i + 1 <<
" has no Sigma";
867 if (!(sigma > 0.0)) {
868 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: presigma #" <<
i + 1 <<
" : Sigma must be positive, got "
873 const Double_t ref{jsEntry.contains(
"Ref") ? jsEntry[
"Ref"].get<
Double_t>() : 0.0};
877 const std::string what{ParamList(jsEntry[
"ParamName"], params)};
879 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: presigma #" <<
i + 1 <<
" : " << what;
884 const Int_t paramIdx{jsEntry[
"ParamIndex"].get<
Int_t>()};
886 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: presigma #" <<
i + 1 <<
" : ParamIndex " << paramIdx
891 params.push_back(paramIdx);
897 const std::string name{jsEntry[
"NodeName"].get<std::string>()};
899 nodes.push_back(node);
901 what =
"unknown NodeName '" + name +
"'";
902 }
else if (hasDetectorID) {
903 const Int_t encID{jsEntry[
"DetectorID"].get<
Int_t>()};
904 const Int_t detIdx{detIdxByID(encID)};
908 what =
Form(
"unknown DetectorID %d (%s)", encID, hierarchy.
DetModel()->
DecodeID(encID).c_str());
910 what = MatchComposites(jsEntry[
"NodeMatch"], hierarchy, nodes);
912 what = LeafScope(hasOver ? jsEntry[
"Over"] : nlohmann::json(
"Elements"), hierarchy, nodes);
914 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: presigma #" <<
i + 1 <<
" : " << what;
918 LOG_IF(warning, nodes.empty()) <<
" ! presigma #" <<
i + 1 <<
" addresses nothing in this model";
920 for (
const Int_t p : params)
921 for (
const NodeRef* node : nodes) {
922 priors.
sigma[node->params[p].full] = sigma;
923 priors.
ref[node->params[p].full] = ref;
928 LOG(error) <<
" <!> BmnAlignConfig::LoadPresigmas: " << errors <<
" error(s) detected, aborting";
937 LOG(warning) <<
" ! no parameter got a prior, proceeding without presigmas";
941 LOG(info) <<
" " << priors.
presigmaCount <<
" of " << hierarchy.
AllParams() <<
" parameters got a prior ("
942 << entriesCnt <<
" entries)";
943 std::array<Int_t, BMN_GLOBAL_PARAMS_PD> perCorrection{};
945 if (priors.
sigma[p.full] > 0.0)
946 perCorrection[p.prm]++;
949 <<
" element(s) constrained";
951 if (!node.IsComposite())
954 for (
const ParamRef& p : node.params)
957 LOG(info) <<
" composite '" << node.name <<
"' : " << (named.
IsNull() ?
"no prior" : named.
Data());