Hyppopy API¶
Main Classes¶
HyppopyProject¶
-
class
hyppopy.HyppopyProject.
HyppopyProject
(config=None)¶ The HyppopyProject class takes care of the optimization settings. An instance can be configured using a config dictionary or by using the hyperparameter and settings methods. In case of initializing via dicts those can be passed to the constructor or by using the set_config method. After initialization a HyppopyProject instance is passed to a solver class which internally checks for consistency with it’s needs. The class distinguished between two categories, hyperparameter and general settings.
The hyperparameter are a dictionary structure as follows and can be accessed via hyperparameter {‘param_name: {‘domain’: ‘uniform’, …}, …}
General settings are internally converted to class attributes and can accessed directly or via settings
An example config could look like: config = {‘hyperparameter’: {‘myparam’: {‘domain’: ‘uniform’, ‘data’: [0, 100], ‘type’: float}, …},
‘my_setting_1’: 3.1415, ‘my_setting_2’: ‘hello world’}project = HyppopyProject(config)
The same can be achieved using: project = HyppopyProject() project.add_hyperparameter(name=’myparam’, domain=’uniform’, data=[0, 100], type=float}) project.add_setting(‘my_setting_1’, 3.1415) project.add_setting(‘my_setting_2’, ‘hello world’)
-
add_hyperparameter
(name, **kwargs)¶ This function can be used to set hyperparameter descriptions. Alternatively use set_hyperparameter to set all at once.
Parameters: - name – [str] hyperparameter name
- kwargs – [dict] configuration dict defining a hyperparameter e.g. domain=’uniform’, data=[1,100], …
-
add_setting
(name, value)¶ This function can be used to set a general settings. Alternatively use set_settings to set all at once.
Parameters: - name – [str] setting name
- value – [object] settings value
-
get_typeof
(name)¶ Returns a hyperparameter type by name
Parameters: name – [str] hyperparameter name Returns: [type] hyperparameter type
-
set_config
(config)¶ Set a config dict
Parameters: config – [dict] configuration dict defining hyperparameter and general settings
-
set_hyperparameter
(params)¶ This function can be used to set the hyperparameter description directly by passing the hyperparameter section of a config dict (see class description). Alternatively use add_hyperparameter to add one after each other.
Parameters: params – [dict] configuration dict defining hyperparameter
-
set_settings
(**kwargs)¶ This function can be used to set the general settings directly by passing the settings as name=value pairs. Alternatively use add_setting to add one after each other.
Parameters: kwargs – [dict] settings dict e.g. my_setting_1=3.1415, my_setting_2=’hello world’, …
-
HyppopySolver¶
-
class
hyppopy.solvers.HyppopySolver.
HyppopySolver
(project=None)¶ The HyppopySolver class is the base class for all solver addons. It defines virtual functions a child class has to implement to deal with the front-end communication, orchestrating the optimization process and ensuring a proper process information storing. The key idea is that the HyppopySolver class defines an interface to configure and run an object instance of itself independently from the concrete solver lib used to optimize in the background. To achieve this goal an addon developer needs to implement the abstract methods ‘convert_searchspace’, ‘execute_solver’. These methods abstract the peculiarities of the solver libs to offer, on the user side, a simple and consistent parameter space configuration and optimization procedure. The method ‘convert_searchspace’ transforms the hyppopy parameter space description into the solver lib specific description. The methods loss_func_cand_preprocess and loss_func_postprocess are used to handle solver lib specifics of calling the actual blackbox function and execute_solver is executed when the run method is invoked und takes care of calling the solver lib solving routine.
The class HyppopySolver defines an interface to be implemented when writing a custom solver. Each solver derivative needs to implement the abstract methods:
- convert_searchspace
- execute_solver
- TODO
- define_interface
The dev-user interface consists of the methods:
- _add_member
- _add_hyperparameter_signature
- _check_project
The end-user interface consists of the methods:
- run
- get_results
- print_best
- print_timestats
- start_viewer
-
accumulated_blackbox_time
¶ Get the summed blackbox function computation time.
Returns: [float] blackbox function computation time
-
best
¶ Returns best parameter set.
Returns: [dict] best parameter set
-
blackbox
¶ Get the BlackboxFunction object.
Returns: [object] BlackboxFunction instance or function
-
convert_searchspace
(hyperparameter)¶ This function gets the unified hyppopy-like parameterspace description as input and, if necessary, should convert it into a solver lib specific format. The function is invoked when run is called and what it returns is passed as searchspace argument to the function execute_solver.
Parameters: hyperparameter – [dict] nested parameter description dict e.g. {‘name’: {‘domain’:’uniform’, ‘data’:[0,1], ‘type’:’float’}, …} Returns: [object] converted hyperparameter space
-
define_interface
()¶ This function is called when HyppopySolver.__init__ function finished. Child classes need to define their individual parameter here by calling the _add_member function for each class member variable need to be defined. Using _add_hyperparameter_signature the structure of a hyperparameter the solver expects must be defined. Both, members and hyperparameter signatures are later get checked, before executing the solver, ensuring settings passed fullfill solver needs.
-
execute_solver
(searchspace)¶ This function is called immediately after convert_searchspace and get the output of the latter as input. It’s purpose is to call the solver libs main optimization function.
Parameters: searchspace – converted hyperparameter space
-
get_results
()¶ This function returns a complete optimization history as pandas DataFrame and a dict with the optimal parameter set.
Returns: [DataFrame], [dict] history and optimal parameter set
-
loss_func_cand_preprocess
(candidates)¶ TODO :param candidates: :return:
-
loss_func_postprocess
(results)¶ TODO :param candidates: :return:
-
loss_function
(**params)¶ This function is called each iteration with a selected parameter set. The parameter set selection is driven by the solver lib itself. This function just calls loss_function_batch() with a batch size of one. It takes care of converting the params to CandidateDescriptors.
Parameters: params – [dict] hyperparameter space sample e.g. {‘p1’: 0.123, ‘p2’: 3.87, …} Returns: [float] loss
-
loss_function_batch
(candidates)¶ This function is called with a list of candidates. This list is driven by the solver lib itself. The purpose of this function is to take care of the iteration reporting and the calling of the callback_func if available. As a developer you might want to overwrite this function (or the ‘non-batch’-version completely (e.g. HyperoptSolver).
Parameters: candidates – [list of CandidateDescriptors] Returns: [dict] result e.g. {‘loss’: 0.5, ‘book_time’: …, ‘refresh_time’: …}
-
loss_function_batch_call
(candidates)¶ TODO :param candidates: :return:
-
print_best
()¶ Optimization result console output printing.
-
print_timestats
()¶ Time statistic console output printing.
-
project
¶ HyppopyProject instance
Returns: [HyppopyProject] project instance
-
run
(print_stats=True)¶ This function starts the optimization process.
Parameters: print_stats – [bool] en- or disable console output
-
solver_overhead
¶ Get the solver overhead, this is the total time minus the duration of the blackbox function calls.
Returns: [float] solver overhead duration
-
start_viewer
(port=8097, server='http://localhost')¶ Starts the visdom viewer.
Parameters: - port – [int] port number, default: 8097
- server – [str] server name, default: http://localhost
-
time_per_iteration
¶ Get the mean duration per iteration.
Returns: [float] time per iteration
-
total_duration
¶ Get total computation duration.
Returns: [float] total computation time
-
trials
¶ Get the Trials instance.
Returns: [object] Trials instance
BlackboxFunction¶
-
class
hyppopy.BlackboxFunction.
BlackboxFunction
(**kwargs)¶ This class is a BlackboxFunction wrapper class encapsulating the loss function. Additional function pointer can be set to get access at different pipelining steps:
- dataloader_func: data loading, the function must return a data object and is called first when the solver is executed.
- The data object returned will be the input of the blackbox function.
- preprocess_func: data preprocessing is called after dataloader_func, the functions signature must be foo(data, params)
- and must return a data object. The input is the data object set directly or via dataloader_func, the params are passed from constructor params.
- callback_func: this function is called at each iteration step getting passed the trail info content, can be used for
- custom visualization
- data: add a data object directly
The constructor accepts several function pointers or a data object which are all None by default (see below). Additionally one can define an arbitrary number of arg pairs. These are passed as input to each function pointer as arguments.
Parameters: - dataloader_func – data loading function pointer, default=None
- preprocess_func – data preprocessing function pointer, default=None
- callback_func – callback function pointer, default=None
- data – data object, default=None
- kwargs – additional arg=value pairs
-
blackbox_func
¶ BlackboxFunction wrapper class encapsulating the loss function or a function accepting a hyperparameter set and returning a float.
Returns: [object] pointer to blackbox_func
-
callback_func
¶ This function is called at each iteration step getting passed the trail info content, can be used for custom visualization
Returns: [object] callback_func
-
data
¶ Datastructure keeping the input data.
Returns: [object] data
-
dataloader_func
¶ Data loading, the function must return a data object and is called first when the solver is executed. The data object returned will be the input of the blackbox function.
Returns: [object] dataloader_func
-
preprocess_func
¶ Data preprocessing is called after dataloader_func, the functions signature must be foo(data, params) and must return a data object. The input is the data object set directly or via dataloader_func, the params are passed from constructor params.
Returns: [object] preprocess_func
-
raw_data
¶ This data structure is used to store the return from dataloader_func to serve as input for preprocess_func if available.
Returns: [object] raw_data
-
setup
(kwargs)¶ Alternative to Constructor, kwargs signature see __init__
Parameters: kwargs – (see __init__)
SolverPool¶
Solver Classes¶
HyperoptSolver¶
-
class
hyppopy.solvers.HyperoptSolver.
HyperoptSolver
(project=None)¶ -
convert
(param_settings)¶ Convert searchspace to hyperopt specific searchspace
Parameters: param_settings – [dict] hyperparameter description Returns: [object] hyperopt description
-
convert_searchspace
(hyperparameter)¶ This function gets the unified hyppopy-like parameterspace description as input and, if necessary, should convert it into a solver lib specific format. The function is invoked when run is called and what it returns is passed as searchspace argument to the function execute_solver.
Parameters: hyperparameter – [dict] nested parameter description dict e.g. {‘name’: {‘domain’:’uniform’, ‘data’:[0,1], ‘type’:’float’}, …} Returns: [object] converted hyperparameter space
-
define_interface
()¶ This function is called when HyppopySolver.__init__ function finished. Child classes need to define their individual parameter here by calling the _add_member function for each class member variable need to be defined. Using _add_hyperparameter_signature the structure of a hyperparameter the solver expects must be defined. Both, members and hyperparameter signatures are later get checked, before executing the solver, ensuring settings passed fullfill solver needs.
-
execute_solver
(searchspace)¶ This function is called immediately after convert_searchspace and get the output of the latter as input. It’s purpose is to call the solver libs main optimization function.
Parameters: searchspace – converted hyperparameter space
-
loss_func_cand_preprocess
(params)¶ Loss function wrapper function.
Parameters: params – [dict] hyperparameter set Returns: [float] loss
-
loss_func_postprocess
(loss)¶ Loss function wrapper function.
Parameters: params – [dict] hyperparameter set Returns: [float] loss
-
loss_function
(params)¶ Loss function wrapper function.
Parameters: params – [dict] hyperparameter set Returns: [float] loss
-
OptunitySolver¶
-
class
hyppopy.solvers.OptunitySolver.
OptunitySolver
(project=None)¶ -
convert_searchspace
(hyperparameter)¶ This function gets the unified hyppopy-like parameterspace description as input and, if necessary, should convert it into a solver lib specific format. The function is invoked when run is called and what it returns is passed as searchspace argument to the function execute_solver.
Parameters: hyperparameter – [dict] nested parameter description dict e.g. {‘name’: {‘domain’:’uniform’, ‘data’:[0,1], ‘type’:’float’}, …} Returns: [object] converted hyperparameter space
-
define_interface
()¶ This function is called when HyppopySolver.__init__ function finished. Child classes need to define their individual parameter here by calling the _add_member function for each class member variable need to be defined. Using _add_hyperparameter_signature the structure of a hyperparameter the solver expects must be defined. Both, members and hyperparameter signatures are later get checked, before executing the solver, ensuring settings passed fullfill solver needs.
-
execute_solver
(searchspace)¶ This function is called immediately after convert_searchspace and get the output of the latter as input. It’s purpose is to call the solver libs main optimization function.
Parameters: searchspace – converted hyperparameter space
-
split_categorical
(pdict)¶ This function splits the incoming dict into two parts, categorical only entries and other.
Parameters: pdict – [dict] input parameter description dict Returns: [dict],[dict] categorical only, others
-
OptunaSolver¶
-
class
hyppopy.solvers.OptunaSolver.
OptunaSolver
(project=None)¶ -
convert_searchspace
(hyperparameter)¶ This function gets the unified hyppopy-like parameterspace description as input and, if necessary, should convert it into a solver lib specific format. The function is invoked when run is called and what it returns is passed as searchspace argument to the function execute_solver.
Parameters: hyperparameter – [dict] nested parameter description dict e.g. {‘name’: {‘domain’:’uniform’, ‘data’:[0,1], ‘type’:’float’}, …} Returns: [object] converted hyperparameter space
-
define_interface
()¶ This function is called when HyppopySolver.__init__ function finished. Child classes need to define their individual parameter here by calling the _add_member function for each class member variable need to be defined. Using _add_hyperparameter_signature the structure of a hyperparameter the solver expects must be defined. Both, members and hyperparameter signatures are later get checked, before executing the solver, ensuring settings passed fullfill solver needs.
-
execute_solver
(searchspace)¶ This function is called immediately after convert_searchspace and get the output of the latter as input. It’s purpose is to call the solver libs main optimization function.
Parameters: searchspace – converted hyperparameter space
-
get_candidates
(trial=None)¶ This function converts the searchspace to a candidate_list that can then be used to distribute via MPI.
Parameters: searchspace – converted hyperparameter space
-
trial_cache
(trial)¶ Optuna specific loss function wrapper
Parameters: trial – [Trial] instance Returns: [function] loss function
-
RandomsearchSolver¶
-
class
hyppopy.solvers.RandomsearchSolver.
RandomsearchSolver
(project=None)¶ The RandomsearchSolver class implements a randomsearch optimization. The randomsearch supports categorical, uniform, normal and loguniform sampling. The solver draws an independent sample from the parameter space each iteration.
-
convert_searchspace
(hyperparameter)¶ This function gets the unified hyppopy-like parameterspace description as input and, if necessary, should convert it into a solver lib specific format. The function is invoked when run is called and what it returns is passed as searchspace argument to the function execute_solver.
Parameters: hyperparameter – [dict] nested parameter description dict e.g. {‘name’: {‘domain’:’uniform’, ‘data’:[0,1], ‘type’:’float’}, …} Returns: [object] converted hyperparameter space
-
define_interface
()¶ This function is called when HyppopySolver.__init__ function finished. Child classes need to define their individual parameter here by calling the _add_member function for each class member variable need to be defined. Using _add_hyperparameter_signature the structure of a hyperparameter the solver expects must be defined. Both, members and hyperparameter signatures are later get checked, before executing the solver, ensuring settings passed fullfill solver needs.
-
execute_solver
(searchspace)¶ This function is called immediately after convert_searchspace and get the output of the latter as input. It’s purpose is to call the solver libs main optimization function.
Parameters: searchspace – converted hyperparameter space
-
get_candidates
(searchspace)¶ This function converts the searchspace to a candidate_list that can then be used to distribute via MPI.
Parameters: searchspace – converted hyperparameter space
-
-
hyppopy.solvers.RandomsearchSolver.
draw_uniform_sample
(param)¶ Function draws a random sample from a uniform range
Parameters: param – [dict] input hyperparameter discription Returns: random sample value of type data[‘type’]
-
hyppopy.solvers.RandomsearchSolver.
draw_normal_sample
(param)¶ Function draws a random sample from a normal distributed range
Parameters: param – [dict] input hyperparameter discription Returns: random sample value of type data[‘type’]
-
hyppopy.solvers.RandomsearchSolver.
draw_loguniform_sample
(param)¶ Function draws a random sample from a logarithmic distributed range
Parameters: param – [dict] input hyperparameter discription Returns: random sample value of type data[‘type’]
-
hyppopy.solvers.RandomsearchSolver.
draw_categorical_sample
(param)¶ Function draws a random sample from a categorical list
Parameters: param – [dict] input hyperparameter discription Returns: random sample value of type data[‘type’]
-
hyppopy.solvers.RandomsearchSolver.
draw_sample
(param)¶ Function draws a sample from the input hyperparameter descriptor depending on it’s domain
Parameters: param – [dict] input hyperparameter discription Returns: random sample value of type data[‘type’]
QuasiRandomsearchSolver¶
-
class
hyppopy.solvers.QuasiRandomsearchSolver.
HaltonSequenceGenerator
¶ This class generates Halton sequences (https://en.wikipedia.org/wiki/Halton_sequence). The class needs a total number of samples and the number of dimensions to generate a quasirandom sequence for each axis. The method get_unit_space returns a sequence list with N_samples for each axis representing N_samples vectors on a unit sphere.
-
get_unit_space
(N_samples, N_dims)¶ Returns a unit space in form of a sequence list keeping N_dims sequences with N_sample samplings. Each sample represents a N_dims dimensional vector on a unit sphere.
Parameters: - N_samples – [int] Number of samples
- N_dims – [int] Number of dimensions
Returns: [list] samples list of length N_dims keeping lists each of length N_samples
-
-
class
hyppopy.solvers.QuasiRandomsearchSolver.
QuasiRandomSampleGenerator
(N_samples=None)¶ This class takes care of the hyperparameter space creation and next sample delivery.
-
generate_samples
(N_samples=None)¶ This function is called once when the first sample is requested. It generates the halton sequence space.
Parameters: N_samples – [int] number of samples
-
next
()¶ Returns the next sample. Returns None if all samples are requested.
Returns: [dict] sample dict {‘name’:value, …}
-
set_axis
(name, data, domain, dtype)¶ Add an axis description.
Parameters: - name – [str] axis name
- data – [list] axis range [min, max]
- domain – [str] axis domain
- dtype – [type] axis data type
-
-
class
hyppopy.solvers.QuasiRandomsearchSolver.
QuasiRandomsearchSolver
(project=None)¶ The QuasiRandomsearchSolver class implements a quasi randomsearch optimization. The quasi randomsearch supports categorical and uniform sampling. The solver defines a Halton Sequence distributed hyperparameter space. This means a rather evenly distributed space sampling but no real randomness.
-
convert_searchspace
(hyperparameter)¶ This function gets the unified hyppopy-like parameterspace description as input and, if necessary, should convert it into a solver lib specific format. The function is invoked when run is called and what it returns is passed as searchspace argument to the function execute_solver.
Parameters: hyperparameter – [dict] nested parameter description dict e.g. {‘name’: {‘domain’:’uniform’, ‘data’:[0,1], ‘type’:’float’}, …} Returns: [object] converted hyperparameter space
-
define_interface
()¶ This function is called when HyppopySolver.__init__ function finished. Child classes need to define their individual parameter here by calling the _add_member function for each class member variable need to be defined. Using _add_hyperparameter_signature the structure of a hyperparameter the solver expects must be defined. Both, members and hyperparameter signatures are later get checked, before executing the solver, ensuring settings passed fullfill solver needs.
-
execute_solver
(searchspace)¶ This function is called immediately after convert_searchspace and get the output of the latter as input. It’s purpose is to call the solver libs main optimization function.
Parameters: searchspace – converted hyperparameter space
-
RandomsearchSolver¶
-
class
hyppopy.solvers.RandomsearchSolver.
RandomsearchSolver
(project=None) The RandomsearchSolver class implements a randomsearch optimization. The randomsearch supports categorical, uniform, normal and loguniform sampling. The solver draws an independent sample from the parameter space each iteration.
-
convert_searchspace
(hyperparameter) This function gets the unified hyppopy-like parameterspace description as input and, if necessary, should convert it into a solver lib specific format. The function is invoked when run is called and what it returns is passed as searchspace argument to the function execute_solver.
Parameters: hyperparameter – [dict] nested parameter description dict e.g. {‘name’: {‘domain’:’uniform’, ‘data’:[0,1], ‘type’:’float’}, …} Returns: [object] converted hyperparameter space
-
define_interface
() This function is called when HyppopySolver.__init__ function finished. Child classes need to define their individual parameter here by calling the _add_member function for each class member variable need to be defined. Using _add_hyperparameter_signature the structure of a hyperparameter the solver expects must be defined. Both, members and hyperparameter signatures are later get checked, before executing the solver, ensuring settings passed fullfill solver needs.
-
execute_solver
(searchspace) This function is called immediately after convert_searchspace and get the output of the latter as input. It’s purpose is to call the solver libs main optimization function.
Parameters: searchspace – converted hyperparameter space
-
get_candidates
(searchspace) This function converts the searchspace to a candidate_list that can then be used to distribute via MPI.
Parameters: searchspace – converted hyperparameter space
-
-
hyppopy.solvers.RandomsearchSolver.
draw_uniform_sample
(param) Function draws a random sample from a uniform range
Parameters: param – [dict] input hyperparameter discription Returns: random sample value of type data[‘type’]
-
hyppopy.solvers.RandomsearchSolver.
draw_normal_sample
(param) Function draws a random sample from a normal distributed range
Parameters: param – [dict] input hyperparameter discription Returns: random sample value of type data[‘type’]
-
hyppopy.solvers.RandomsearchSolver.
draw_loguniform_sample
(param) Function draws a random sample from a logarithmic distributed range
Parameters: param – [dict] input hyperparameter discription Returns: random sample value of type data[‘type’]
-
hyppopy.solvers.RandomsearchSolver.
draw_categorical_sample
(param) Function draws a random sample from a categorical list
Parameters: param – [dict] input hyperparameter discription Returns: random sample value of type data[‘type’]
-
hyppopy.solvers.RandomsearchSolver.
draw_sample
(param) Function draws a sample from the input hyperparameter descriptor depending on it’s domain
Parameters: param – [dict] input hyperparameter discription Returns: random sample value of type data[‘type’]
Helpers¶
VisdomViewer¶
-
class
hyppopy.VisdomViewer.
VisdomViewer
(project, port=8097, server='http://localhost')¶ The VisdomViewer class implements the live viewer plots via visdom. When extending implement your plot as methos and call it in update. Using this class make it necessary starting a visdom server beforehand $ python -m visdom.server
-
plot_hyperparameter
(input_data)¶ This function plots each hyperparameter axis
Parameters: input_data – [dict] trail infos
-
plot_losshistory
(input_data)¶ This function plots the loss history loss over iteration
Parameters: input_data – [dict] trail infos
-
show_best
(input_data)¶ Shows best parameter set
Parameters: input_data – [dict] trail infos
-
show_statusreport
(input_data)¶ This function prints status report per iteration
Parameters: input_data – [dict] trail infos
-
update
(input_data)¶ This function calls all visdom displaying routines
Parameters: input_data – [dict] trail infos
-
FunctionSimulator¶
-
class
hyppopy.FunctionSimulator.
FunctionSimulator
¶ The FunctionSimulator class serves as simulation tool for solver testing and evaluation purposes. It’s designed to simulate an energy functional by setting axis data for each dimension via binary image files. The binary image files are sampled and a range interval is read from a config file. The class implements __call__ to act like a blackbox function when initialized.
f=f(x1,x2,…,xn) [for n binary images and n range config files
as image input .png grayscale images are expected as range config .cfg ascii files are expected containing
-
add_dimension
(data, x_range)¶ Add dimension data
Parameters: - data – [object] axis data
- x_range – [list] data absolute range
-
clear
()¶ Clears all data structures
-
dims
()¶ Returns the dimensions of the data obejct
Returns: [int] shape[0]
-
get_axis_settings
(section)¶ Extract axis settings
Parameters: section – [str] config section Returns: [dict] axis options
-
load_default
(name='3D')¶ load default images as axis
Parameters: name – [str] subfolder name
-
load_images
(path)¶ Load axis images and config files from path.
Parameters: path – [str] data path
-
minima
()¶ computes the minimum for each axis
Returns: [list] minima per axis
-
plot
(dim=None, title='')¶ Plots the dimension.
Parameters: - dim – [int] axis index
- title – [str] plot title
-
pos_to_indices
(positions)¶ Converts real positions to index
Parameters: positions – [list] positions Returns: [list], [list], [list] left, right fraction
-
range
(dim)¶ Returns the data range
Returns: [float] range
-
read_config
(fname)¶ Read a config file.
Parameters: fname – [str] config file name Returns: [ConfigParser] config
-
sample_image
(img, dim)¶ Samples an image to extract function value list.
Parameters: - img – [ndarray] image
- dim – [int] axis index
-
size
()¶ Returns the size of the data obejct
Returns: [int] shape[2]
-
Singleton¶
-
hyppopy.Singleton.
singleton_object
(cls)¶ Class decorator that transforms (and replaces) a class definition (which must have a Singleton metaclass) with the actual singleton object. Ensures that the resulting object can still be “instantiated” (i.e., called), returning the same object. Also ensures the object can be pickled, is hashable, and has the correct string representation (the name of the singleton)