Commit 30731788 authored by Paul Bauer's avatar Paul Bauer Committed by Mark Abraham
Browse files

Modernize legend writing for xvg files

Fixed possible bugs with legend indexing in some cases.
parent 42761670
......@@ -322,13 +322,7 @@ void AbstractPlotModule::dataStarted(AbstractAnalysisData* /* data */)
}
if (output_env_get_print_xvgr_codes(oenv) && !impl_->legend_.empty())
{
std::vector<const char*> legend;
legend.reserve(impl_->legend_.size());
for (size_t i = 0; i < impl_->legend_.size(); ++i)
{
legend.push_back(impl_->legend_[i].c_str());
}
xvgr_legend(impl_->fp_, legend.size(), legend.data(), oenv);
xvgrLegend(impl_->fp_, impl_->legend_, oenv);
}
}
}
......
......@@ -41,6 +41,8 @@
#include <ctime>
#include <memory>
#include <utility>
#include <vector>
#include "gromacs/commandline/filenm.h"
#include "gromacs/domdec/domdec_struct.h"
......@@ -75,6 +77,7 @@
#include "gromacs/utility/logger.h"
#include "gromacs/utility/smalloc.h"
#include "gromacs/utility/strconvert.h"
#include "gromacs/utility/stringutil.h"
namespace
{
......@@ -2494,58 +2497,29 @@ static void init_edsamstate(const gmx_edsam& ed, edsamhistory_t* EDstate)
}
}
/* Adds 'buf' to 'str' */
static void add_to_string(char** str, const char* buf)
static void nice_legend(std::vector<std::string>* setname,
std::string* LegendStr,
const std::string& value,
const std::string& unit,
char EDgroupchar)
{
int len;
len = strlen(*str) + strlen(buf) + 1;
srenew(*str, len);
strcat(*str, buf);
auto tmp = gmx::formatString("%c %s", EDgroupchar, value.c_str());
LegendStr->append(gmx::formatString(EDcol_sfmt, tmp.c_str()));
tmp += gmx::formatString(" (%s)", unit.c_str());
setname->emplace_back(tmp);
}
static void add_to_string_aligned(char** str, const char* buf)
static void nice_legend_evec(std::vector<std::string>* setname,
std::string* LegendStr,
t_eigvec* evec,
char EDgroupChar,
const std::string& EDtype)
{
char buf_aligned[STRLEN];
sprintf(buf_aligned, EDcol_sfmt, buf);
add_to_string(str, buf_aligned);
}
static void nice_legend(const char*** setname,
int* nsets,
char** LegendStr,
const char* value,
const char* unit,
char EDgroupchar)
{
auto tmp = gmx::formatString("%c %s", EDgroupchar, value);
add_to_string_aligned(LegendStr, tmp.c_str());
tmp += gmx::formatString(" (%s)", unit);
(*setname)[*nsets] = gmx_strdup(tmp.c_str());
(*nsets)++;
}
static void nice_legend_evec(const char*** setname,
int* nsets,
char** LegendStr,
t_eigvec* evec,
char EDgroupChar,
const char* EDtype)
{
int i;
char tmp[STRLEN];
for (i = 0; i < evec->neig; i++)
for (int i = 0; i < evec->neig; i++)
{
sprintf(tmp, "EV%dprj%s", evec->ieig[i], EDtype);
nice_legend(setname, nsets, LegendStr, tmp, "nm", EDgroupChar);
auto tmp = gmx::formatString("EV%dprj%s", evec->ieig[i], EDtype.c_str());
nice_legend(setname, LegendStr, tmp, "nm", EDgroupChar);
}
}
......@@ -2553,18 +2527,16 @@ static void nice_legend_evec(const char*** setname,
/* Makes a legend for the xvg output file. Call on MASTER only! */
static void write_edo_legend(gmx_edsam* ed, int nED, const gmx_output_env_t* oenv)
{
int i;
int nr_edi, nsets, n_flood, n_edsam;
const char** setname;
char buf[STRLEN];
char* LegendStr = nullptr;
int n_flood, n_edsam;
std::vector<std::string> setname;
std::string LegendStr;
auto edi = ed->edpar.begin();
fprintf(ed->edo, "# Output will be written every %d step%s\n", edi->outfrq, edi->outfrq != 1 ? "s" : "");
for (nr_edi = 1; nr_edi <= nED; nr_edi++)
for (int nr_edi = 1; nr_edi <= nED; nr_edi++)
{
fprintf(ed->edo, "#\n");
fprintf(ed->edo,
......@@ -2618,23 +2590,7 @@ static void write_edo_legend(gmx_edsam* ed, int nED, const gmx_output_env_t* oen
++edi;
}
/* Print a nice legend */
snew(LegendStr, 1);
LegendStr[0] = '\0';
sprintf(buf, "# %6s", "time");
add_to_string(&LegendStr, buf);
/* Calculate the maximum number of columns we could end up with */
edi = ed->edpar.begin();
nsets = 0;
for (nr_edi = 1; nr_edi <= nED; nr_edi++)
{
nsets += 5 + edi->vecs.mon.neig + edi->vecs.linfix.neig + edi->vecs.linacc.neig
+ edi->vecs.radfix.neig + edi->vecs.radacc.neig + edi->vecs.radcon.neig
+ 6 * edi->flood.vecs.neig;
++edi;
}
snew(setname, nsets);
LegendStr.append(gmx::formatString("# %6s", "time"));
/* In the mdrun time step in a first function call (do_flood()) the flooding
* forces are calculated and in a second function call (do_edsam()) the
......@@ -2642,120 +2598,94 @@ static void write_edo_legend(gmx_edsam* ed, int nED, const gmx_output_env_t* oen
* over the edi groups and output first the flooding, then the ED part */
/* The flooding-related legend entries, if flooding is done */
nsets = 0;
if (EssentialDynamicsType::Flooding == ed->eEDtype)
{
edi = ed->edpar.begin();
for (nr_edi = 1; nr_edi <= nED; nr_edi++)
auto edi = ed->edpar.begin();
for (int nr_edi = 1; nr_edi <= nED; nr_edi++)
{
/* Always write out the projection on the flooding EVs. Of course, this can also
* be achieved with the monitoring option in do_edsam() (if switched on by the
* user), but in that case the positions need to be communicated in do_edsam(),
* which is not necessary when doing flooding only. */
nice_legend(&setname, &nsets, &LegendStr, "RMSD to ref", "nm", get_EDgroupChar(nr_edi, nED));
nice_legend(&setname, &LegendStr, "RMSD to ref", "nm", get_EDgroupChar(nr_edi, nED));
for (i = 0; i < edi->flood.vecs.neig; i++)
for (int i = 0; i < edi->flood.vecs.neig; i++)
{
sprintf(buf, "EV%dprjFLOOD", edi->flood.vecs.ieig[i]);
nice_legend(&setname, &nsets, &LegendStr, buf, "nm", get_EDgroupChar(nr_edi, nED));
auto buf = gmx::formatString("EV%dprjFLOOD", edi->flood.vecs.ieig[i]);
nice_legend(&setname, &LegendStr, buf, "nm", get_EDgroupChar(nr_edi, nED));
/* Output the current reference projection if it changes with time;
* this can happen when flooding is used as harmonic restraint */
if (edi->flood.bHarmonic && edi->flood.referenceProjectionSlope[i] != 0.0)
{
sprintf(buf, "EV%d ref.prj.", edi->flood.vecs.ieig[i]);
nice_legend(&setname, &nsets, &LegendStr, buf, "nm", get_EDgroupChar(nr_edi, nED));
auto buf = gmx::formatString("EV%d ref.prj.", edi->flood.vecs.ieig[i]);
nice_legend(&setname, &LegendStr, buf, "nm", get_EDgroupChar(nr_edi, nED));
}
/* For flooding we also output Efl, Vfl, deltaF, and the flooding forces */
if (0 != edi->flood.tau) /* only output Efl for adaptive flooding (constant otherwise) */
{
sprintf(buf, "EV%d-Efl", edi->flood.vecs.ieig[i]);
nice_legend(&setname, &nsets, &LegendStr, buf, "kJ/mol", get_EDgroupChar(nr_edi, nED));
auto buf = gmx::formatString("EV%d-Efl", edi->flood.vecs.ieig[i]);
nice_legend(&setname, &LegendStr, buf, "kJ/mol", get_EDgroupChar(nr_edi, nED));
}
sprintf(buf, "EV%d-Vfl", edi->flood.vecs.ieig[i]);
nice_legend(&setname, &nsets, &LegendStr, buf, "kJ/mol", get_EDgroupChar(nr_edi, nED));
buf = gmx::formatString("EV%d-Vfl", edi->flood.vecs.ieig[i]);
nice_legend(&setname, &LegendStr, buf, "kJ/mol", get_EDgroupChar(nr_edi, nED));
if (0 != edi->flood.tau) /* only output deltaF for adaptive flooding (zero otherwise) */
{
sprintf(buf, "EV%d-deltaF", edi->flood.vecs.ieig[i]);
nice_legend(&setname, &nsets, &LegendStr, buf, "kJ/mol", get_EDgroupChar(nr_edi, nED));
auto buf = gmx::formatString("EV%d-deltaF", edi->flood.vecs.ieig[i]);
nice_legend(&setname, &LegendStr, buf, "kJ/mol", get_EDgroupChar(nr_edi, nED));
}
sprintf(buf, "EV%d-FLforces", edi->flood.vecs.ieig[i]);
nice_legend(&setname, &nsets, &LegendStr, buf, "kJ/mol/nm", get_EDgroupChar(nr_edi, nED));
buf = gmx::formatString("EV%d-FLforces", edi->flood.vecs.ieig[i]);
nice_legend(&setname, &LegendStr, buf, "kJ/mol/nm", get_EDgroupChar(nr_edi, nED));
}
++edi;
} /* End of flooding-related legend entries */
}
n_flood = nsets;
n_flood = setname.size();
/* Now the ED-related entries, if essential dynamics is done */
edi = ed->edpar.begin();
for (nr_edi = 1; nr_edi <= nED; nr_edi++)
for (int nr_edi = 1; nr_edi <= nED; nr_edi++)
{
if (bNeedDoEdsam(*edi)) /* Only print ED legend if at least one ED option is on */
{
nice_legend(&setname, &nsets, &LegendStr, "RMSD to ref", "nm", get_EDgroupChar(nr_edi, nED));
nice_legend(&setname, &LegendStr, "RMSD to ref", "nm", get_EDgroupChar(nr_edi, nED));
/* Essential dynamics, projections on eigenvectors */
nice_legend_evec(&setname,
&nsets,
&LegendStr,
&edi->vecs.mon,
get_EDgroupChar(nr_edi, nED),
"MON");
nice_legend_evec(&setname,
&nsets,
&LegendStr,
&edi->vecs.linfix,
get_EDgroupChar(nr_edi, nED),
"LINFIX");
nice_legend_evec(&setname,
&nsets,
&LegendStr,
&edi->vecs.linacc,
get_EDgroupChar(nr_edi, nED),
"LINACC");
nice_legend_evec(&setname,
&nsets,
&LegendStr,
&edi->vecs.radfix,
get_EDgroupChar(nr_edi, nED),
"RADFIX");
nice_legend_evec(
&setname, &LegendStr, &edi->vecs.mon, get_EDgroupChar(nr_edi, nED), "MON");
nice_legend_evec(
&setname, &LegendStr, &edi->vecs.linfix, get_EDgroupChar(nr_edi, nED), "LINFIX");
nice_legend_evec(
&setname, &LegendStr, &edi->vecs.linacc, get_EDgroupChar(nr_edi, nED), "LINACC");
nice_legend_evec(
&setname, &LegendStr, &edi->vecs.radfix, get_EDgroupChar(nr_edi, nED), "RADFIX");
if (edi->vecs.radfix.neig)
{
nice_legend(&setname, &nsets, &LegendStr, "RADFIX radius", "nm", get_EDgroupChar(nr_edi, nED));
nice_legend(&setname, &LegendStr, "RADFIX radius", "nm", get_EDgroupChar(nr_edi, nED));
}
nice_legend_evec(&setname,
&nsets,
&LegendStr,
&edi->vecs.radacc,
get_EDgroupChar(nr_edi, nED),
"RADACC");
nice_legend_evec(
&setname, &LegendStr, &edi->vecs.radacc, get_EDgroupChar(nr_edi, nED), "RADACC");
if (edi->vecs.radacc.neig)
{
nice_legend(&setname, &nsets, &LegendStr, "RADACC radius", "nm", get_EDgroupChar(nr_edi, nED));
nice_legend(&setname, &LegendStr, "RADACC radius", "nm", get_EDgroupChar(nr_edi, nED));
}
nice_legend_evec(&setname,
&nsets,
&LegendStr,
&edi->vecs.radcon,
get_EDgroupChar(nr_edi, nED),
"RADCON");
nice_legend_evec(
&setname, &LegendStr, &edi->vecs.radcon, get_EDgroupChar(nr_edi, nED), "RADCON");
if (edi->vecs.radcon.neig)
{
nice_legend(&setname, &nsets, &LegendStr, "RADCON radius", "nm", get_EDgroupChar(nr_edi, nED));
nice_legend(&setname, &LegendStr, "RADCON radius", "nm", get_EDgroupChar(nr_edi, nED));
}
}
++edi;
} /* end of 'pure' essential dynamics legend entries */
n_edsam = nsets - n_flood;
n_edsam = setname.size() - n_flood;
xvgr_legend(ed->edo, nsets, setname, oenv);
sfree(setname);
xvgrLegend(ed->edo, setname, oenv);
fprintf(ed->edo,
"#\n"
......@@ -2764,8 +2694,7 @@ static void write_edo_legend(gmx_edsam* ed, int nED, const gmx_output_env_t* oen
1 == n_flood ? "" : "s",
n_edsam,
1 == n_edsam ? "" : "s");
fprintf(ed->edo, "%s", LegendStr);
sfree(LegendStr);
fprintf(ed->edo, "%s", LegendStr.c_str());
fflush(ed->edo);
}
......
......@@ -45,6 +45,7 @@
#include "gromacs/fileio/gmxfio.h"
#include "gromacs/fileio/oenv.h"
#include "gromacs/math/vec.h"
#include "gromacs/utility/arrayref.h"
#include "gromacs/utility/basedefinitions.h"
#include "gromacs/utility/binaryinformation.h"
#include "gromacs/utility/coolstuff.h"
......@@ -328,15 +329,8 @@ static bool stringIsEmpty(const std::string& s)
return s.empty();
}
static bool stringIsEmpty(const char* s)
void xvgrLegend(FILE* out, gmx::ArrayRef<const std::string> setNames, const struct gmx_output_env_t* oenv)
{
return (s == nullptr || s[0] == '\0');
}
template<typename T>
static void xvgr_legend(FILE* out, int nsets, const T* setname, const gmx_output_env_t* oenv)
{
int i;
char buf[STRLEN];
if (output_env_get_print_xvgr_codes(oenv))
......@@ -347,56 +341,47 @@ static void xvgr_legend(FILE* out, int nsets, const T* setname, const gmx_output
fprintf(out, "@ legend loctype view\n");
fprintf(out, "@ legend %g, %g\n", 0.78, 0.8);
fprintf(out, "@ legend length %d\n", 2);
for (i = 0; (i < nsets); i++)
int currentSet = 0;
for (const auto& name : setNames)
{
if (!stringIsEmpty(setname[i]))
if (!stringIsEmpty(name))
{
if (output_env_get_xvg_format(oenv) == XvgFormat::Xmgr)
{
fprintf(out, "@ legend string %d \"%s\"\n", i, xvgrstr(setname[i], oenv, buf, STRLEN));
fprintf(out, "@ legend string %d \"%s\"\n", currentSet, xvgrstr(name, oenv, buf, STRLEN));
}
else
{
fprintf(out, "@ s%d legend \"%s\"\n", i, xvgrstr(setname[i], oenv, buf, STRLEN));
fprintf(out, "@ s%d legend \"%s\"\n", currentSet, xvgrstr(name, oenv, buf, STRLEN));
}
}
++currentSet;
}
}
}
void xvgrLegend(FILE* out, const std::vector<std::string>& setNames, const struct gmx_output_env_t* oenv)
{
xvgr_legend(out, setNames.size(), setNames.data(), oenv);
}
void xvgr_legend(FILE* out, int nsets, const char* const* setnames, const struct gmx_output_env_t* oenv)
{
xvgr_legend<const char*>(out, nsets, setnames, oenv);
}
void xvgr_new_dataset(FILE* out, int nr_first, int nsets, const char** setname, const gmx_output_env_t* oenv)
void xvgrNewDataset(FILE* out, int nr_first, gmx::ArrayRef<const std::string> setNames, const gmx_output_env_t* oenv)
{
int i;
char buf[STRLEN];
if (output_env_get_print_xvgr_codes(oenv))
{
fprintf(out, "@\n");
for (i = 0; (i < nsets); i++)
int currentSet = nr_first;
for (const auto& name : setNames)
{
if (setname[i])
if (!name.empty())
{
if (output_env_get_xvg_format(oenv) == XvgFormat::Xmgr)
{
fprintf(out,
"@ legend string %d \"%s\"\n",
i + nr_first,
xvgrstr(setname[i], oenv, buf, STRLEN));
fprintf(out, "@ legend string %d \"%s\"\n", currentSet, xvgrstr(name, oenv, buf, STRLEN));
}
else
{
fprintf(out, "@ s%d legend \"%s\"\n", i + nr_first, xvgrstr(setname[i], oenv, buf, STRLEN));
fprintf(out, "@ s%d legend \"%s\"\n", currentSet, xvgrstr(name, oenv, buf, STRLEN));
}
}
++currentSet;
}
}
else
......@@ -817,15 +802,21 @@ gmx::MultiDimArray<std::vector<double>, gmx::dynamicExtents2D> readXvgData(const
return xvgDataAsArrayTransposed;
}
void write_xvg(const char* fn, const char* title, int nx, int ny, real** y, const char** leg, const gmx_output_env_t* oenv)
void write_xvg(const char* fn,
const char* title,
int nx,
int ny,
real** y,
gmx::ArrayRef<const std::string> leg,
const gmx_output_env_t* oenv)
{
FILE* fp;
int i, j;
fp = xvgropen(fn, title, "X", "Y", oenv);
if (leg)
if (!leg.empty())
{
xvgr_legend(fp, ny - 1, leg, oenv);
xvgrLegend(fp, leg, oenv);
}
for (i = 0; (i < nx); i++)
{
......
......@@ -45,7 +45,11 @@
#include "gromacs/utility/real.h"
struct gmx_output_env_t;
namespace gmx
{
template<typename>
class ArrayRef;
} // namespace gmx
/***************************************************
* XVGR DEFINITIONS
***************************************************/
......@@ -170,18 +174,21 @@ void xvgr_view(FILE* out, real xmin, real ymin, real xmax, real ymax, const stru
void xvgr_world(FILE* out, real xmin, real ymin, real xmax, real ymax, const struct gmx_output_env_t* oenv);
/* Set the world in xvgr */
void xvgrLegend(FILE* out, const std::vector<std::string>& setNames, const struct gmx_output_env_t* oenv);
/* Make a legend box, and also modifies the view to make room for the legend */
void xvgr_legend(FILE* out, int nsets, const char* const* setnames, const struct gmx_output_env_t* oenv);
/* Make a legend box, and also modifies the view to make room for the legend */
//! Prepare a legend box, also modifies the view to make room for the legend
void xvgrLegend(FILE* out, gmx::ArrayRef<const std::string> setNames, const struct gmx_output_env_t* oenv);
void xvgr_new_dataset(FILE* out, int nr_first, int nsets, const char** setnames, const struct gmx_output_env_t* oenv);
/* End the previous data set(s) and start new one(s).
nr_first = the global set number of the first new set (or 0 if no legend)
nsets = the number of sets (or 0 if no legends)
setnames = the set names (or NULL if no legends)
/*! \brief
* End the previous data set(s) and start new one(s).
*
* \param[in] out File to write to.
* \param[in] firstSetNumber Global number of the first data set, or 0 if no legend.
* \param[in] setNames View on collection of strings for legend in the data set.
* \param[in] oenv Global output enivornment handling.
*/
void xvgrNewDataset(FILE* out,
int firstSetNumber,
gmx::ArrayRef<const std::string> setNames,
const struct gmx_output_env_t* oenv);
void xvgr_line_props(FILE* out, int NrSet, int LineStyle, int LineColor, const struct gmx_output_env_t* oenv);
/* Set xvgr line styles and colors */
......@@ -233,13 +240,13 @@ int read_xvg(const char* fn, double*** y, int* ny);
gmx::MultiDimArray<std::vector<double>, gmx::dynamicExtents2D> readXvgData(const std::string& fn);
void write_xvg(const char* fn,
const char* title,
int nx,
int ny,
real** y,
const char** leg,
const struct gmx_output_env_t* oenv);
void write_xvg(const char* fn,
const char* title,
int nx,
int ny,
real** y,
gmx::ArrayRef<const std::string> leg,
const struct gmx_output_env_t* oenv);
/* Write a two D array (y) of dimensions nx rows times
* ny columns to a file. If leg != NULL it will be written too.
*/
......
......@@ -418,15 +418,15 @@ static void estimate_error(const char* eefile,
gmx_bool bAllowNegLTCorr,
const gmx_output_env_t* oenv)
{
FILE* fp;
int bs, prev_bs, nbs, nb;
real spacing, nbr;
int s, i, j;
double blav, var;
char** leg;
real * tbs, *ybs, rtmp, dens, *fitsig, twooe, tau1_est, tau_sig;
double fitparm[3];
real ee, a, tau1, tau2;
FILE* fp;
int bs, prev_bs, nbs, nb;
real spacing, nbr;
int s, i, j;
double blav, var;
std::vector<std::string> leg;
real * tbs, *ybs, rtmp, dens, *fitsig, twooe, tau1_est, tau_sig;
double fitparm[3];
real ee, a, tau1, tau2;
if (n < 4)
{
......@@ -440,9 +440,8 @@ static void estimate_error(const char* eefile,
{
fprintf(fp, "@ subtitle \"using block averaging, total time %g (%d points)\"\n", (n - 1) * dt, n);
}
snew(leg, 2 * nset);
xvgr_legend(fp, 2 * nset, leg, oenv);
sfree(leg);
leg.resize(2 * nset);
xvgrLegend(fp, leg, oenv);
spacing = std::pow(2.0, 1.0 / resol);
snew(tbs, n);
......
......@@ -59,6 +59,7 @@
#include "gromacs/utility/gmxassert.h"
#include "gromacs/utility/smalloc.h"
#include "gromacs/utility/snprintf.h"
#include "gromacs/utility/stringutil.h"
/* Structure for the names of lambda vector components */
......@@ -981,15 +982,14 @@ static void sample_coll_make_hist(sample_coll_t* sc, std::vector<int>* bin, doub
/* write a collection of histograms to a file */
static void sim_data_histogram(sim_data_t* sd, const char* filename, int nbin_default, const gmx_output_env_t* oenv)
{
char label_x[STRLEN];
const char * dhdl = "dH/d\\lambda", *deltag = "\\DeltaH", *lambda = "\\lambda";
const char* title = "N(\\DeltaH)";
const char* label_y = "Samples";
FILE* fp;
lambda_data_t* bl;
int nsets = 0;
char** setnames = nullptr;
gmx_bool first_set = FALSE;
char label_x[STRLEN];
const char * dhdl = "dH/d\\lambda", *deltag = "\\DeltaH", *lambda = "\\lambda";
const char* title = "N(\\DeltaH)";
const char* label_y = "Samples";
FILE* fp;
lambda_data_t* bl;
std::vector<std::string> setnames;
gmx_bool first_set = FALSE;
/* histogram data: */
std::vector<int> hist;
double dx = 0;
......@@ -1013,26 +1013,24 @@ static void sim_data_histogram(sim_data_t* sd, const char* filename, int nbin_de