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

Update plots #76

Merged
merged 17 commits into from
May 25, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions anvil/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,36 @@ def parse_bootstrap_sample_size(self, n_boot: int):
log.warning(f"Using user specified bootstrap sample size: {n_boot}")
return n_boot

def produce_bootstrap_seed(
self, manual_bootstrap_seed: (int, type(None)) = None):
def produce_bootstrap_seed(self, manual_bootstrap_seed: (int, type(None)) = None):
if manual_bootstrap_seed is None:
return randint(0, maxsize)
# numpy is actually this strict but let's keep it sensible.
if (manual_bootstrap_seed < 0) or (manual_bootstrap_seed > 2**32):
if (manual_bootstrap_seed < 0) or (manual_bootstrap_seed > 2 ** 32):
raise ConfigError("Seed is outside of appropriate range: [0, 2 ** 32]")
return manual_bootstrap_seed

def parse_cosh_fit_min_separation(self, n: int, training_geometry):
Copy link
Owner

Choose a reason for hiding this comment

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

can this not just take the lattice_length directly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yep, good point

Copy link
Collaborator Author

@jmarshrossney jmarshrossney May 20, 2021

Choose a reason for hiding this comment

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

I can replace training_geometry.length with lattice_length in this parse function, but not the production rule directly below. I'm sure by now I should understand this behaviour but alas, I forget.

Also, since we're moving towards removing training_context (which contains the entire training runcard) in favour of things more akin to training_geometry which just extract the specific parameters we need (#62), perhaps it does make sense to leave this as is.

Copy link
Owner

Choose a reason for hiding this comment

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

that's really weird, I don't understand that..

Copy link
Owner

Choose a reason for hiding this comment

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

I guess leave it for now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

apologies, it doesn't work with either the parse or produce - the parse just worked because I was using the default value for cosh_fit_min_separation in cosh_fit_window..

Copy link
Owner

Choose a reason for hiding this comment

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

I think it's because that key is only present in the training context and so we could in theory put lattice length but we'd have to collect over the fit or write the key in the sampling runcard, idk why I didn't think about this earlier.

So definitely leave it for now and I will look into extracting parameters from the trained model in a more satisfactory way..

Copy link
Owner

Choose a reason for hiding this comment

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

aka the issue you linked.

"""The smallest lattice separation to include in when fitting a cosh function
to the correlator, so as to the extract the correlation length.

See also: ``produce_cosh_fit_window``.
"""
if n > training_geometry.length // 2 - 2:
raise ConfigError("Not enough points to for a three-parameter fit.")
return n

def produce_cosh_fit_window(self, training_geometry, cosh_fit_min_separation=None):
"""Window of values corresponding to lattice separations, within which to fit
a cosh function to the correlator, so as to the extract the correlation length.
"""
if cosh_fit_min_separation is None:
return slice(1, None) # include all but (0, 0) separation by default

return slice(
cosh_fit_min_separation,
training_geometry.length - cosh_fit_min_separation + 1,
)

@element_of("windows")
def parse_window(self, window: float):
"""A numerical factor featuring in the calculation of the optimal 'window'
Expand All @@ -215,4 +236,4 @@ def produce_use_multiprocessing(self):
"""Don't use mp on MacOS"""
if platform.system() == "Darwin":
return False
return True
return True
72 changes: 36 additions & 36 deletions anvil/observables.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,46 @@

log = logging.getLogger(__name__)


def cosh_shift(x, xi, A, c):
return A * np.cosh(-x / xi) + c


def fit_zero_momentum_correlator(zero_momentum_correlator, training_geometry):
# TODO should I bootstrap this whole process...?

T = training_geometry.length
# TODO: would be good to specify this in runcard
t0 = T // 4
window = slice(t0, T - t0 + 1)

t = np.arange(T)
y = zero_momentum_correlator.mean(axis=-1)
yerr = zero_momentum_correlator.std(axis=-1)

try:
popt, pcov = optim.curve_fit(
cosh_shift,
xdata=t[window] - T // 2,
ydata=y[window],
sigma=yerr[window],
def fit_zero_momentum_correlator(
zero_momentum_correlator, training_geometry, cosh_fit_window=slice(1, None)
jmarshrossney marked this conversation as resolved.
Show resolved Hide resolved
):
t = np.arange(training_geometry.length) - training_geometry.length // 2

# fit for each correlation func in the bootstrap ensemble
optimised_parameters = []
for correlator in zero_momentum_correlator.transpose():
try:
popt, pcov = optim.curve_fit(
cosh_shift,
xdata=t[cosh_fit_window],
ydata=correlator[cosh_fit_window],
)
optimised_parameters.append(popt)
except RuntimeError:
pass

n_boot = zero_momentum_correlator.shape[-1]
failures = n_boot - len(optimised_parameters)
if failures > 0:
log.warning(
f"Failed to fit cosh to correlation function for {failures}/{n_boot} members of the bootstrap ensemble."
)
return (popt, pcov, t0)
except RuntimeError:
log.warning("Failed to fit cosh to correlation function.")
return None
if failures >= n_boot - 1:
log.warning("Too many failures: no fit parameters will be returned.")
return None
jmarshrossney marked this conversation as resolved.
Show resolved Hide resolved

xi, A, c = np.array(optimised_parameters).transpose()
return xi, A, c


def correlation_length_from_fit(fit_zero_momentum_correlator):
popt, pcov, _ = fit_zero_momentum_correlator
return popt[0], np.sqrt(pcov[0, 0])
xi, _, _ = fit_zero_momentum_correlator
return xi


def autocorrelation(chain):
Expand Down Expand Up @@ -111,7 +119,7 @@ def magnetic_susceptibility(magnetization, abs_magnetization_squared):


def magnetization_series(configs):
return configs.sum(axis=1)
return configs.sum(axis=1).numpy()
Copy link
Owner

Choose a reason for hiding this comment

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

would it make more sense to just cast the configs numpy earlier instead of multiple times on the observables?

Copy link
Owner

Choose a reason for hiding this comment

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

in other words, do we ever need configs to be a tensor?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah it would. I mean I think it only happens the once if you discount the unused correlator calculation, but it would probably be better to cast to numpy at the end of the sampling.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'd rather just leave this niggle until we're messing around inside sample.py anyway, trying to generate configurations from each layer, if that's alright.



def magnetization_autocorr(magnetization_series):
Expand Down Expand Up @@ -144,7 +152,9 @@ def __two_point_correlator(
axis=-1 # sample average
)

return correlator.reshape((training_geometry.length, training_geometry.length, -1))
return correlator.reshape(
(training_geometry.length, training_geometry.length, -1)
).numpy()


def two_point_correlator(
Expand Down Expand Up @@ -210,16 +220,6 @@ def ising_energy(two_point_correlator):
return (two_point_correlator[1, 0] + two_point_correlator[0, 1]) / 2


def inverse_pole_mass(effective_pole_mass, training_geometry):
T = training_geometry.length
t0 = T // 4
window = slice(t0, T - t0 + 1)

xi = np.reciprocal(effective_pole_mass)[window]

return np.nanmean(xi, axis=0) # average over "large" t points


def second_moment_correlation_length(two_point_correlator, susceptibility):
"""Second moment correlation length, defined as the normalised second
moment of the two point correlator."""
Expand Down
Loading