Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
Jammy2211 committed Jul 23, 2024
2 parents ed5c582 + a468317 commit e88af1f
Show file tree
Hide file tree
Showing 33 changed files with 1,814 additions and 1,516 deletions.
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
5 changes: 4 additions & 1 deletion autofit/database/model/fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,10 @@ def __getitem__(self, item: str):
"""
for p in self.jsons + self.arrays + self.hdus + self.pickles:
if p.name == item:
return p.value
value = p.value
if item == "samples_summary":
value.model = self.model
return value

return getattr(self, item)

Expand Down
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

0 comments on commit e88af1f

Please sign in to comment.