Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to plot multimodal posteriors when the option is enabled. To … #451

Merged
merged 11 commits into from
Dec 13, 2024
78 changes: 77 additions & 1 deletion xpsi/PostProcessing/_runs.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,25 @@ def likelihood(self, obj):

@classmethod
def load_runs(cls, ID, run_IDs, roots, base_dirs, use_nestcheck,
likelihood=None, **kwargs):
likelihood=None,multi_mode=False,mode_label="mode", **kwargs):
""" Construct a :class:`~.Runs` instance by loading distinct runs.

param bool multi_mode:
Load the runs so that they are separated in different modes found
by a MultiNest run with mmodal=True. Works at the moment only when
loading a single run and when having models with the same number of
modes.

param string mode_label:
Select a label for the modes to be shown in the plots.

The kwargs will be shared by nested sampling runs. The args must be
lists that will be zipped to instantiate a set of run backends.

"""
mode_label ="_"+mode_label
cls.mode_label = mode_label
cls.multi_mode = multi_mode
# if there is a transform method available, try to wrap it
# so that error due to a mismatch in parameter order is bypassed
if likelihood is not None:
Expand All @@ -122,10 +134,64 @@ def _transform(q, **kwargs):
_transform = None
_overwrite = False

if multi_mode:
if len(roots)>1:
raise Exception("Error: multi_mode=True is not yet working when loading multiple runs.")
try:
cls.mode_len
except:
pass
else:
print("Warning: A run/model using multi_mode=True was previously already loaded. This load will overwrite the number of modes found, and can result in an error if trying to plot the previously loaded results.")
for vec in range(len(roots)):
filerootpath =_os.path.join(base_dirs[vec], roots[vec])
# Read the file and manually handle blank lines
with open(filerootpath+"post_separate.dat", 'r') as file:
lines = file.readlines()

# Convert lines to a numpy array, replacing blank lines with a marker (e.g., None or NaN)
data = []
for line in lines:
stripped = line.strip()
if stripped: # Non-blank line
data.append(_np.array(stripped.split(), dtype=float))
else: # Blank line
data.append(None)

# Separate modes based on None markers
modes = []
current_mode = []
for row in data:
if row is None:
if current_mode:
modes.append(_np.array(current_mode))
current_mode = []
else:
current_mode.append(row)

# Append the last mode if not empty
if current_mode:
modes.append(_np.array(current_mode))
for mode in range(len(modes)):
_np.savetxt(filerootpath+f"mode{mode+1}.txt", modes[mode])
roots.append(roots[vec]+f"mode{mode+1}")
run_IDs.append(run_IDs[vec]+f"{mode_label} {mode+1}")
base_dirs.append(base_dirs[vec])
use_nestcheck.append(use_nestcheck[vec])
# Forget about the default xpsi loaded file
roots = roots[1:]
run_IDs = run_IDs[1:]
base_dirs = base_dirs[1:]
use_nestcheck = use_nestcheck[1:]

cls.mode_len =len(modes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it it be this line that is the problem? Is this saving the number of modes in some model specific variable, or to something that gets overwritten every time a new model is loaded?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the mode_len should be run-specific, since even different runs can have different number of modes.

Copy link
Contributor

@thjsal thjsal Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think there are a few issues. First of all, the number of modes (and the other mode info too) is saved to a class variable that gets overwritten every time runs to new models are loaded. Secondly, this does not seem to work (at least in a general case) either if loading many runs for the same model, since it assumes that all the runs have the same number of modes.

I am not sure if there is an easy way to fix it, but maybe we can just raise errors/warnings if somebody tries to use this with multiple models/runs, or at least warn about this in the docstring. EDIT: I have done that now.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A solution for the 1st issue would be to make multi-modes variables (like mode_len) not class variables. This can be achieved by replacing return cls(runs, likelihood, ID, **kwargs) by something like
Runs = cls(runs, likelihood, ID, **kwargs)
Runs.mode_len =len(modes)
return Runs

For the second issue, this can be fixed by storing the mode_len and other such values in form of a dictionary, with the keys being the run ID and the values the mode_len ?

If you agree I can do it quickly

Copy link
Contributor

@thjsal thjsal Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, those sounds like good ideas to me. Please go ahead if you like. I think you should then also change mode_label to not be a class variable either (so that different models can have different labels).

And I guess we still need to assume that if we set multi_mode=True, all the loaded runs should have been run using the MultiNest set to mmodal=True? EDIT: Or if not, we can make that an array of booleans instead of one boolean.



runs = []
for root, run_ID, base_dir, check in zip(roots, run_IDs,
base_dirs,
use_nestcheck):

runs.append(NestedBackend(root, base_dir,
ID=run_ID,
use_nestcheck=check,
Expand All @@ -143,6 +209,16 @@ def set_subset(self, IDs=None, combine=False, combine_all=False,
if IDs is None:
self._subset = self._runs
else:
if self.multi_mode:
old_IDs=IDs
IDs=[]
for ID in old_IDs:
if self.mode_label in ID:
IDs = old_IDs
else:
root_ID=ID
for mode in range(self.mode_len):
IDs.append(root_ID + f"{self.mode_label} {mode+1}")
self._subset = [self[ID] for ID in IDs]

if combine and force_combine: # create new run object
Expand Down
Loading