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

Feature/howtofit docs #1022

Merged
merged 7 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ We define our model, a 1D Gaussian by writing a Python class using the format be
This method will be used to fit the model to data and compute a likelihood.
"""

def model_data_1d_via_xvalues_from(self, xvalues):
def model_data_from(self, xvalues):

transformed_xvalues = xvalues - self.centre

Expand Down Expand Up @@ -133,12 +133,12 @@ To fit this Gaussian to the ``data`` we create an Analysis object, which gives *

"""
We fit the ``data`` with the Gaussian instance, using its
"model_data_1d_via_xvalues_from" function to create the model data.
"model_data_from" function to create the model data.
"""

xvalues = np.arange(self.data.shape[0])

model_data = instance.model_data_1d_via_xvalues_from(xvalues=xvalues)
model_data = instance.model_data_from(xvalues=xvalues)
residual_map = self.data - model_data
chi_squared_map = (residual_map / self.noise_map) ** 2.0
log_likelihood = -0.5 * sum(chi_squared_map)
Expand Down
7 changes: 0 additions & 7 deletions autofit/config/priors/Gaussian.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
GaussianPrior:
lower_limit:
type: Constant
value: -inf
upper_limit:
type: Constant
value: inf
centre:
gaussian_limits:
lower: -inf
Expand Down
40 changes: 40 additions & 0 deletions autofit/config/priors/Gaussian2D.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
centre_0:
gaussian_limits:
lower: -inf
upper: inf
lower_limit: 0.0
type: Uniform
upper_limit: 100.0
width_modifier:
type: Absolute
value: 20.0
centre_1:
gaussian_limits:
lower: -inf
upper: inf
lower_limit: 0.0
type: Uniform
upper_limit: 100.0
width_modifier:
type: Absolute
value: 20.0
normalization:
gaussian_limits:
lower: 0.0
upper: inf
lower_limit: 1.0e-06
type: LogUniform
upper_limit: 1000000.0
width_modifier:
type: Relative
value: 0.5
sigma:
gaussian_limits:
lower: 0.0
upper: inf
lower_limit: 0.0
type: Uniform
upper_limit: 25.0
width_modifier:
type: Relative
value: 0.5
8 changes: 4 additions & 4 deletions autofit/example/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ def model_data_1d_from(self, instance: af.ModelInstance) -> np.ndarray:
The way this is generated changes depending on if the model is a `Model` (therefore having only one profile)
or a `Collection` (therefore having multiple profiles).

If its a model, the model component's `model_data_1d_via_xvalues_from` is called and the output returned.
For a collection, each components `model_data_1d_via_xvalues_from` is called, iterated through and summed
If its a model, the model component's `model_data_from` is called and the output returned.
For a collection, each components `model_data_from` is called, iterated through and summed
to return the combined model data.

Parameters
Expand All @@ -103,13 +103,13 @@ def model_data_1d_from(self, instance: af.ModelInstance) -> np.ndarray:
try:
for profile in instance:
try:
model_data_1d += profile.model_data_1d_via_xvalues_from(
model_data_1d += profile.model_data_from(
xvalues=xvalues
)
except AttributeError:
pass
except TypeError:
model_data_1d += instance.model_data_1d_via_xvalues_from(xvalues=xvalues)
model_data_1d += instance.model_data_from(xvalues=xvalues)

return model_data_1d

Expand Down
10 changes: 5 additions & 5 deletions autofit/example/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

The log_likelihood_function in the Analysis class receives an instance of this classes where the values of its
parameters have been set up according to the non-linear search. Because instances of the classes are used, this means
their methods (e.g. model_data_1d_via_xvalues_from) can be used in the log likelihood function.
their methods (e.g. model_data_from) can be used in the log likelihood function.
"""


Expand Down Expand Up @@ -56,7 +56,7 @@ def __eq__(self, other):
and self.sigma == other.sigma
)

def model_data_1d_via_xvalues_from(self, xvalues: np.ndarray) -> np.ndarray:
def model_data_from(self, xvalues: np.ndarray) -> np.ndarray:
"""
Calculate the normalization of the profile on a 1D grid of Cartesian x coordinates.

Expand Down Expand Up @@ -91,7 +91,7 @@ def __call__(self, xvalues: np.ndarray) -> np.ndarray:
xvalues
The x coordinates in the original reference frame of the grid.
"""
return self.model_data_1d_via_xvalues_from(xvalues=xvalues)
return self.model_data_from(xvalues=xvalues)

def inverse(self, y):
"""
Expand Down Expand Up @@ -129,7 +129,7 @@ def __init__(
self.normalization = normalization
self.rate = rate

def model_data_1d_via_xvalues_from(self, xvalues: np.ndarray) -> np.ndarray:
def model_data_from(self, xvalues: np.ndarray) -> np.ndarray:
"""
Calculate the 1D Gaussian profile on a 1D grid of Cartesian x coordinates.

Expand All @@ -156,7 +156,7 @@ def __call__(self, xvalues: np.ndarray) -> np.ndarray:
values
The x coordinates in the original reference frame of the grid.
"""
return self.model_data_1d_via_xvalues_from(xvalues=xvalues)
return self.model_data_from(xvalues=xvalues)


class PhysicalNFW:
Expand Down
15 changes: 11 additions & 4 deletions autofit/example/visualize.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,18 @@ def visualize(
"""

xvalues = np.arange(analysis.data.shape[0])
model_data_1d = np.zeros(analysis.data.shape[0])
model_data_1d_list = []

try:
for profile in instance:
try:
model_data_1d += profile.model_data_1d_via_xvalues_from(xvalues=xvalues)
model_data_1d_list.append(profile.model_data_from(xvalues=xvalues))
except AttributeError:
pass
except TypeError:
model_data_1d += instance.model_data_1d_via_xvalues_from(xvalues=xvalues)
model_data_1d_list.append(instance.model_data_from(xvalues=xvalues))

model_data_1d = sum(model_data_1d_list)

plt.errorbar(
x=xvalues,
Expand All @@ -116,7 +118,12 @@ def visualize(
capsize=2,
)
plt.plot(xvalues, model_data_1d, color="r")
plt.title("Model fit to 1D Gaussian + Exponential dataset.")

for model_data_1d_profile in model_data_1d_list:

plt.plot(xvalues, model_data_1d_profile, color="b", linestyle="--")

plt.title("Model fit to multiple 1D profiles dataset.")
plt.xlabel("x values of profile")
plt.ylabel("Profile normalization")

Expand Down
5 changes: 4 additions & 1 deletion autofit/non_linear/fitness.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,10 @@ def check_log_likelihood(self, fitness):
except FileNotFoundError:
return

max_log_likelihood_sample = samples_summary.max_log_likelihood_sample
try:
max_log_likelihood_sample = samples_summary.max_log_likelihood_sample
except AttributeError:
return
log_likelihood_old = samples_summary.max_log_likelihood_sample.log_likelihood

parameters = max_log_likelihood_sample.parameter_lists_for_model(model=self.model)
Expand Down
14 changes: 7 additions & 7 deletions autofit/non_linear/initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,14 @@ def samples_from_model(
if os.environ.get("PYAUTOFIT_TEST_MODE") == "1" and test_mode_samples:
return self.samples_in_test_mode(total_points=total_points, model=model)

logger.info(
f"Generating initial samples of model, which are subject to prior limits and other constraints. "
f"Using {n_cores} cores."
)

unit_parameter_lists = []
parameter_lists = []
figures_of_merit_list = []

sneaky_pool = SneakyPool(n_cores, fitness, paths)

logger.info(f"Generating initial samples of model using {n_cores} cores")

while len(figures_of_merit_list) < total_points:
remaining_points = total_points - len(figures_of_merit_list)
batch_size = min(remaining_points, n_cores)
Expand All @@ -96,8 +93,9 @@ def samples_from_model(

for figure_of_merit, unit_parameter_list, parameter_list in zip(
sneaky_pool.map(
self.figure_of_metric,
[(fitness, parameter_list) for parameter_list in parameter_lists_],
function=self.figure_of_metric,
args_list=[(fitness, parameter_list) for parameter_list in parameter_lists_],
log_info=False
),
unit_parameter_lists_,
parameter_lists_,
Expand All @@ -124,6 +122,8 @@ def samples_from_model(
"""
)

logger.info(f"Initial samples generated, starting non-linear search")

return unit_parameter_lists, parameter_lists, figures_of_merit_list

def samples_in_test_mode(self, total_points: int, model: AbstractPriorModel):
Expand Down
5 changes: 3 additions & 2 deletions autofit/non_linear/parallel/sneaky.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ def __init__(
def processes(self):
return self._processes

def map(self, function, args_list):
def map(self, function, args_list, log_info : bool = True):
"""
Execute the function with the given arguments across all of the
processes. The likelihood argument is removed from each args in
Expand All @@ -258,7 +258,8 @@ def map(self, function, args_list):
for args in args_list
]

logger.info(f"Running {len(jobs)} jobs across {self.processes} processes")
if log_info:
logger.info(f"Running {len(jobs)} jobs across {self.processes} processes")

for i, job in enumerate(jobs):
process = self.processes[i % len(self.processes)]
Expand Down
4 changes: 4 additions & 0 deletions autofit/non_linear/paths/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ def image_path(self) -> Path:
"""
The path to the image folder.
"""

if not os.path.exists(self.output_path / "image"):
os.makedirs(self.output_path / "image")

return self.output_path / "image"

@property
Expand Down
55 changes: 41 additions & 14 deletions autofit/non_linear/search/abstract_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ def configure_handler(func):
def decorated(self, *args, **kwargs):
if not should_output("search_log"):
return func(self, *args, **kwargs)
if self.disable_output:
return func(self, *args, **kwargs)
try:
os.makedirs(
self.paths.output_path,
Expand Down Expand Up @@ -151,6 +153,11 @@ def __init__(
"""
super().__init__()

if name is None and path_prefix is None:
self.disable_output = True
else:
self.disable_output = False

from autofit.non_linear.paths.database import DatabasePaths

try:
Expand Down Expand Up @@ -606,6 +613,8 @@ class represented by model M and gives a score for their fitness.
search_internal=result.search_internal,
)

self.logger.info("Search complete, returning result")

return result

def pre_fit_output(
Expand Down Expand Up @@ -640,12 +649,17 @@ def pre_fit_output(
(e.g. as `files/info.json`) and can be loaded via the database.
"""

self.logger.info(f"The output path of this fit is {self.paths.output_path}")
if not self.disable_output:
self.logger.info(f"The output path of this fit is {self.paths.output_path}")
else:
self.logger.info("Output to hard-disk disabled, input a search name to enable.")

if not self.paths.is_complete or self.force_pickle_overwrite:
self.logger.info(
f"Outputting pre-fit files (e.g. model.info, visualization)."
)

if not self.disable_output:
self.logger.info(
f"Outputting pre-fit files (e.g. model.info, visualization)."
)

self.paths.save_all(
search_config_dict=self.config_dict_search,
Expand Down Expand Up @@ -827,7 +841,9 @@ def post_fit_output(self, search_internal, bypass_nuclear_if_on: bool):
else:
self.output_search_internal(search_internal=search_internal)

self.logger.info("Removing all files except for .zip file")
if not self.disable_output:
self.logger.info("Removing all files except for .zip file")

self.paths.zip_remove()

if not bypass_nuclear_if_on:
Expand Down Expand Up @@ -940,14 +956,17 @@ def perform_update(
"""

self.iterations += self.iterations_per_update
if during_analysis:
self.logger.info(
f"""Fit Running: Updating results after {self.iterations} iterations (see output folder)."""
)
else:
self.logger.info(
"Fit Complete: Updating final results (see output folder)."
)

if not self.disable_output:

if during_analysis:
self.logger.info(
f"""Fit Running: Updating results after {self.iterations} iterations (see output folder)."""
)
else:
self.logger.info(
"Fit Complete: Updating final results (see output folder)."
)

if not isinstance(self.paths, DatabasePaths) and not isinstance(
self.paths, NullPaths
Expand All @@ -966,8 +985,16 @@ def perform_update(
self.paths.save_samples_summary(samples_summary=samples_summary)

samples_save = samples

log_message = True

if during_analysis:
log_message = False
elif self.disable_output:
log_message = False

samples_save = samples_save.samples_above_weight_threshold_from(
log_message=not during_analysis
log_message=log_message
)
self.paths.save_samples(samples=samples_save)

Expand Down
2 changes: 0 additions & 2 deletions autofit/non_linear/search/nest/dynesty/search/static.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@ def __init__(

self.use_gradient = use_gradient

self.logger.debug("Creating DynestyStatic Search")

@property
def search_internal(self):
return StaticSampler.restore(self.checkpoint_file)
Expand Down
Loading
Loading