Пример #1
0
class TaburuState(State):
    def __init__(self):
        self.parameters = IndexedOrderedDict()
        self.methods = IndexedOrderedDict()

    def apply(self, event):
        if isinstance(event, ParametersAdded):
            if event.table_name in self.parameters:
                self.parameters[event.table_name].append(event.parameters)
            else:
                self.parameters[event.table_name] = ParameterTable(
                    event.parameters)
        elif isinstance(event, MethodAdded):
            if self.methods.get(event.name) is None:
                self.methods[event.name] = event.parameter_indices
            else:
                print("Method name already exists")
                return False
        return True

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        parameters = [
            '{} {}: {}'.format(n, parameter_name, len(table))
            for n, (parameter_name,
                    table) in enumerate(self.parameters.items())
        ]
        methods = [
            "{}: {}".format(method_name, parameters)
            for method_name, parameters in self.methods.items()
        ]
        output = "Parameters:\n   "
        output += "\n   ".join(parameters)
        output += "\nMethods:\n   "
        output += "\n   ".join(methods)
        return output
class GenerateComplexPictures(Exception):
    def __init__(
            self,
            modulo=10.,

            # n1_x=500,
            # scale=16,
            # scale_y=1.,
            # x_offset=8,
            # y_offset=8,
            nx=None,
            ny=None,
            max_length=None,
            x_center=None,
            y_center=None,
            delta=0.0001,
            func_str=None,
            main_folder="images/",
            root_folder=None,
            number=None):

        self.modulo = modulo
        self.delta = delta

        self.all_symbols_16 = np.array(list("0123456789ABCDEF"))
        self.all_symbols_64 = np.array(
            list(string.ascii_letters + string.digits + "-_"))

        self.file_extension_name = "_{}_{}".format(
            self.get_date_time_str(), self.get_random_string_base_16(16))

        # TODO: need a big fix!
        if nx != None and ny != None and max_length != None and x_center != None and y_center != None:
            self.nx = nx
            self.ny = ny
            self.max_length = max_length

            scale_x = max_length
            scale_y = max_length
            if nx > ny:
                scale_y = max_length * ny / nx
            else:
                scale_x = max_length * nx / ny

            self.scale_x = scale_x
            self.scale_y = scale_y

            self.x_center = x_center
            self.y_center = y_center

            self.xs1 = np.arange(
                0, self.nx
            ) / self.nx * self.scale_x - self.scale_x / 2 + self.x_center + self.delta
            self.ys1 = np.arange(
                0, self.ny
            ) / self.ny * self.scale_y - self.scale_y / 2 + self.y_center + self.delta

            self.x_min = self.x_center - self.scale_x / 2
            self.x_max = self.x_center + self.scale_x / 2
            self.y_min = self.y_center - self.scale_y / 2
            self.y_max = self.y_center + self.scale_y / 2

            # globals()["xs1"] = self.xs1
            # globals()["ys1"] = self.ys1

            # print("\nself.nx: {}".format(self.nx))
            # print("self.ny: {}".format(self.ny))

            # print("\nself.max_length: {}".format(self.max_length))
            # print("self.scale_x: {}".format(self.scale_x))
            # print("self.scale_y: {}".format(self.scale_y))
            # print("self.x_center: {}".format(self.x_center))
            # print("self.y_center: {}".format(self.y_center))

            # print("TEST!")
            # sys.exit(-10)
        else:
            raise Exception
            # self.n1_x = n1_x
            # self.scale = scale
            # self.scale_y = scale_y

            # self.x_offset = x_offset
            # self.y_offset = y_offset
            # self.n1_y = int(self.n1_x*self.scale_y)

            # self.xs1 = np.arange(0, self.n1_x)/self.n1_x*self.scale-self.x_offset+self.delta
            # self.ys1 = np.arange(0, self.n1_y)/self.n1_y*self.scale*self.scale_y-self.y_offset+self.delta

        self.ys1 = self.ys1[::-1]

        self.ys1_2d = np.zeros((self.ys1.shape[0], self.xs1.shape[0]))
        self.xs1_2d = self.ys1_2d.copy()

        self.xs1_2d[:] = self.xs1
        self.ys1_2d[:] = self.ys1.reshape((-1, 1))

        self.arr_xy_real_imag = np.vectorize(complex)(self.xs1_2d, self.ys1_2d)

        self.message = self._construct_message()
        super(GenerateComplexPictures, self).__init__(self.message)

        self.main_folder = main_folder
        if main_folder != "images/":
            main_folder += ("" if "/" == main_folder[-1] else "/")
            self.main_folder

        if root_folder == None:
            self.root_folder = ""
        else:
            self.root_folder = root_folder + ("/" if root_folder[-1] != "/"
                                              else "")

        if number != None:
            self.number = number
            self.num_str = "_{:03}".format(number)

            self.z_func_file_name = "z_func{}.txt".format(self.num_str)
            self.path_folder_images = self.main_folder + self.root_folder + "z_funcs/"
            self.path_dir_arrs_data = self.main_folder + self.root_folder + "dm_objs/"
        else:
            self.number = None
            self.num_str = ""
            self.z_func_file_name = "z_func_{}.txt".format(
                self.file_extension_name)
            self.path_folder_images = self.main_folder + "{}{}/".format(
                self.root_folder, self.file_extension_name)

            self.path_dir_arrs_data = self.path_folder_images

        if not os.path.exists(self.path_folder_images):
            os.makedirs(self.path_folder_images)

        if func_str != None:
            self.func_str = func_str

            with open(self.path_folder_images + self.z_func_file_name,
                      "w") as fout:
                fout.write(self.func_str)
        else:
            generate_generic_z_function.main(
                path_folder=self.path_folder_images,
                number=self.number,
                file_extension_name=self.file_extension_name)

            with open(self.path_folder_images + self.z_func_file_name,
                      "r") as fin:
                self.func_str = fin.read()

        print("func_str: {}".format(self.func_str))
        self.func_str_complete = "lambda z: " + self.func_str

        self.f = self.get_f()

        print("self.path_folder_images: {}".format(self.path_folder_images))
        print("self.func_str_complete: {}".format(self.func_str_complete))

        # self.num_str = ""
        # if self.number != None:
        #     self.num_str = "_{:03}".format(self.number)

    def get_random_string_base_16(self, n):
        l = np.random.randint(0, 16, (n, ))
        return "".join(self.all_symbols_16[l])

    def delete_image_folder(self):
        path_folder_images = self.path_folder_images

        print("Remove the whole folder '{}'!".format(path_folder_images))
        shutil.rmtree(path_folder_images)

    def get_random_string_base_64(self, n):
        l = np.random.randint(0, 64, (n, ))
        return "".join(self.all_symbols_64[l])

    def get_date_time_str(self):
        dt = datetime.datetime.now()
        return "Y{:04}_m{:02}_d{:02}_H{:02}_M{:02}_S{:02}_f{:06}".format(
            dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
            dt.microsecond)
        # return "Y{}_m{}_d{}_H{}_M{}_S{}_f{}".format(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond)
        # return "{:%Y_%m_%d_%H_%M_%S_%f}".format(datetime.datetime.now())

    def get_f(self):
        modulo = self.modulo
        f_str = self.func_str_complete

        f = eval(f_str)

        def f_temp(z):
            n_z_orig = f(z)

            length = np.abs(n_z_orig)

            # Is needed for a continous modulo calculation!
            length_mod = length % modulo
            scale = (length_mod if int(length / modulo) % 2 == 0 else modulo -
                     length_mod) / length

            return n_z_orig, scale

        return f_temp

    def calculate_arrs(self):
        print("Calculate self.arr_f and self.arr_scales")
        self.arr_f, self.arr_scales = np.vectorize(self.f)(
            self.arr_xy_real_imag)

        print("Calculate arr")
        arr_angle = np.angle(self.arr_f)
        idx = arr_angle < 0
        arr_angle[idx] = arr_angle[idx] + np.pi * 2

        print("np.min(arr_angle): {}".format(np.min(arr_angle)))
        print("np.max(arr_angle): {}".format(np.max(arr_angle)))

        arr_angle_norm = arr_angle / (np.pi * 2)
        print("np.min(arr_angle_norm): {}".format(np.min(arr_angle_norm)))
        print("np.max(arr_angle_norm): {}".format(np.max(arr_angle_norm)))

        print("Calculate abs")
        arr_abs = np.abs(self.arr_f)
        arr_abs_mod = arr_abs * self.arr_scales

        arr_abs_norm = arr_abs / np.max(arr_abs)
        arr_abs_mod_norm = arr_abs_mod / np.max(arr_abs_mod)

        print("Calculate x and y")
        f_norm_axis = lambda v: (lambda v2: v / np.max(v))(v - np.min(v))

        arr_x = self.arr_f.real
        arr_y = self.arr_f.imag
        arr_x_mod = arr_x * self.arr_scales
        arr_y_mod = arr_y * self.arr_scales

        arr_x_norm = f_norm_axis(arr_x)
        arr_y_norm = f_norm_axis(arr_y)
        arr_x_mod_norm = f_norm_axis(arr_x_mod)
        arr_y_mod_norm = f_norm_axis(arr_y_mod)

        self.arrs = IndexedOrderedDict([
            ("angle", arr_angle_norm),
            ("abs", arr_abs_norm),
            ("abs_mod", arr_abs_mod_norm),
            ("x", arr_x_norm),
            ("x_mod", arr_x_mod_norm),
            ("y", arr_y_norm),
            ("y_mod", arr_y_mod_norm),
        ])

    def calculate_rgb_pixs(self):
        print("Getting all data in pix_float array")
        shape = self.arr_xy_real_imag.shape + (3, )
        self.pixs1_dict = IndexedOrderedDict([
            ("f", np.ones(shape)),
            ("f_mod", np.ones(shape)),
            ("angle", np.ones(shape)),
            ("abs", np.ones(shape)),
            ("abs_mod", np.ones(shape)),
            ("x", np.ones(shape)),
            ("x_mod", np.ones(shape)),
            ("y", np.ones(shape)),
            ("y_mod", np.ones(shape)),
        ])

        self.pixs1_dict["f"][:, :, 0] = self.arrs["angle"]
        self.pixs1_dict["f"][:, :, 2] = self.arrs["abs"] * 1 / 3 + 2 / 3

        self.pixs1_dict["f_mod"][:, :, 0] = self.arrs["angle"]
        self.pixs1_dict["f_mod"][:, :, 2] = self.arrs["abs_mod"]

        keys = ["angle", "abs", "abs_mod", "x", "x_mod", "y", "y_mod"]
        for key in keys:
            self.pixs1_dict[key][:, :, 0] = self.arrs[key]

        hsv_to_rgb_vectorized = np.vectorize(colorsys.hsv_to_rgb)

        print("Convert hsv to rgb and convert to uint8")

        self.pixs1_dict_rgb = IndexedOrderedDict()
        for key, value in self.pixs1_dict.items():
            pix_float_rgb = np.dstack(
                hsv_to_rgb_vectorized(value[..., 0], value[..., 1], value[...,
                                                                          2]))
            self.pixs1_dict_rgb[key] = (pix_float_rgb * 255.9).astype(np.uint8)

        self.pixs1 = [v for v in self.pixs1_dict_rgb.values()]

        self.imgs_orig = [Image.fromarray(pix.copy()) for pix in self.pixs1]

    def add_coordinate_lines(self):
        print("Adding coordinates x and y if possible")
        find_x = 0.
        find_y = 0.
        rows, cols = self.pixs1[0].shape[:2]
        line_col = np.argmin((self.xs1 - find_x)**2)
        line_row = np.argmin((self.ys1 - find_y)**2)

        if not (line_row < 0 or line_row >= rows):
            for pix in self.pixs1:
                pix[:, line_col] = pix[:, line_col] ^ (0xFF, ) * 3

        if not (line_col < 0 or line_col >= cols):
            for pix in self.pixs1:
                pix[line_row, :] = pix[line_row, :] ^ (0xFF, ) * 3

    def add_side_info(self):
        print("Creating the side infos")
        # this is needed for the info on the left side of the graph!
        pix2 = np.zeros((self.pixs1_dict_rgb["f"].shape[0], 300, 3),
                        dtype=np.uint8)
        img2 = Image.fromarray(pix2)

        # get a font
        fnt = ImageFont.truetype('monofonto.ttf', 16)
        d = ImageDraw.Draw(img2)
        d.text((8, 8), "Used function:", font=fnt, fill=(255, 255, 255))

        func_str_split = [
            self.func_str_complete[30 * i:30 * (i + 1)]
            for i in range(0,
                           len(self.func_str_complete) // 30 + 1)
        ]
        for i, func_str_part in enumerate(func_str_split, 1):
            d.text((8, 8 + 24 * i),
                   func_str_part,
                   font=fnt,
                   fill=(255, 255, 255))

        font_y_next = 8 + 24 * (i + 2)
        d.text(
            (8, font_y_next),
            "x_min: {:3.02f}, x_max: {:3.02f}".format(self.x_min, self.x_max),
            font=fnt,
            fill=(255, 255, 255))
        # d.text((8, font_y_next), "x_min: {:3.02f}, x_max: {:3.02f}".format(-self.x_offset, self.scale-self.x_offset), font=fnt, fill=(255, 255, 255))

        font_y_next += 24
        d.text(
            (8, font_y_next),
            "y_min: {:3.02f}, y_max: {:3.02f}".format(self.y_min, self.y_max),
            font=fnt,
            fill=(255, 255, 255))
        # d.text((8, font_y_next), "y_min: {:3.02f}, y_max: {:3.02f}".format(-self.y_offset, self.scale*self.scale_y-self.y_offset), font=fnt, fill=(255, 255, 255))

        pix2 = np.array(img2).copy()

        font_y_next += 24

        modulo_str = "modulo: {:3.02f}".format(self.modulo)
        text_to_write_lst = [("only with f(z)", ),
                             (
                                 "only with f(z)",
                                 modulo_str,
                             ), ("only with angle", ), ("only with abs", ),
                             ("only with abs", modulo_str), ("only with x", ),
                             ("only with x", modulo_str), ("only with y", ),
                             ("only with y", modulo_str)]

        self.pixs2 = []
        for text_to_write in text_to_write_lst:
            img2_temp = img2.copy()
            d = ImageDraw.Draw(img2_temp)

            font_y_next_temp = font_y_next
            for text in text_to_write:
                d.text((8, font_y_next_temp),
                       text,
                       font=fnt,
                       fill=(255, 255, 255))
                font_y_next_temp += 24

            self.pixs2.append(np.array(img2_temp))

        self.imgs = []
        for pix2, pix1 in zip(self.pixs2, self.pixs1):
            pix3 = np.hstack((pix2, pix1))
            self.imgs.append(Image.fromarray(pix3))

    def save_all_images(self):
        self.suffixes = [
            "f", "f_mod", "angle", "abs", "abs_mod", "x", "x_mod", "y", "y_mod"
        ]

        if self.root_folder != "":
            for suffix, img in zip(self.suffixes, self.imgs_orig):
                folder_path_f_orig = self.main_folder + "" + self.root_folder + "orig_" + suffix + "/"
                if not os.path.exists(folder_path_f_orig):
                    os.makedirs(folder_path_f_orig)

                img.save(folder_path_f_orig +
                         "{}_{}.png".format(suffix, self.num_str))
                # img.save(folder_path_f_orig+"{}{}_{}_{}.png".format(suffix, self.num_str,
                #             self.get_date_time_str(),
                #             self.get_random_string_base_16(16))
                # )

            for suffix, img in zip(self.suffixes, self.imgs):
                folder_path = self.main_folder + "" + self.root_folder + "plot_" + suffix + "/"
                if not os.path.exists(folder_path):
                    os.makedirs(folder_path)

                img.save(folder_path +
                         "{}_{}.png".format(suffix, self.num_str))
                # img.save(folder_path+suffix+"{}_{}_{}.png".format(
                #         self.num_str,
                #         self.get_date_time_str(),
                #         self.get_random_string_base_16(16)
                #     )
                # )
        else:
            # print("NO root_folder!!!!")
            for suffix, img in zip(self.suffixes, self.imgs_orig):
                folder_path_f_orig = self.main_folder + "plots_originals/orig_" + suffix + "/"
                if not os.path.exists(folder_path_f_orig):
                    os.makedirs(folder_path_f_orig)

                img.save(folder_path_f_orig + "{}{}{}.png".format(
                    suffix, self.num_str, self.file_extension_name))

            for suffix, img in zip(self.suffixes, self.imgs):
                folder_path_f_orig = self.main_folder + "plots_with_side_info/orig_" + suffix + "/"
                if not os.path.exists(folder_path_f_orig):
                    os.makedirs(folder_path_f_orig)

                img.save(folder_path_f_orig + "{}{}{}.png".format(
                    suffix, self.num_str, self.file_extension_name))

            for suffix, img in zip(self.suffixes, self.imgs):
                if self.number != None:
                    suffix += "_{}".format(self.number)
                img.save(self.path_folder_images +
                         "{}{}.png".format(suffix, self.file_extension_name))

    def save_arrs_data(self):
        # if self.root_folder == "":
        if not os.path.exists(self.path_dir_arrs_data):
            os.makedirs(self.path_dir_arrs_data)
        print("self.path_dir_arrs_data: {}".format(self.path_dir_arrs_data))

        dm_obj = DotMap()

        dm_obj.func_str = self.func_str
        dm_obj.modulo = self.modulo
        dm_obj.delta = self.delta

        dm_obj.arr_xy_real_imag = self.arr_xy_real_imag
        dm_obj.arr_f = self.arr_f
        dm_obj.arr_scales = self.arr_scales

        dm_obj.nx = self.nx
        dm_obj.ny = self.ny
        dm_obj.max_length = self.max_length
        dm_obj.x_center = self.x_center
        dm_obj.y_center = self.y_center

        if self.number != None:
            self.path_file_arrs = self.path_dir_arrs_data + "dm_obj{}.pkl.gz".format(
                self.num_str)
        else:
            self.path_file_arrs = self.path_dir_arrs_data + "dm_obj{}.pkl.gz".format(
                self.file_extension_name)
        print("self.path_file_arrs: {}".format(self.path_file_arrs))
        with gzip.open(self.path_file_arrs, "wb") as fout:
            dill.dump(dm_obj, fout)

    # TODO: make it so that you can call this functions from outside too
    # with the self object! So that an extern z func array can be ploted too!
    def do_calculations(self):
        self.calculate_arrs()

        self.calculate_rgb_pixs()

        self.add_coordinate_lines()

        self.add_side_info()

        self.save_all_images()

        self.save_arrs_data()

    def save_new_z_function(self):
        func_str_complete = self.func_str_complete

        path_folder_data = "data/"
        if not os.path.exists(path_folder_data):
            os.makedirs(path_folder_data)

        path_file_data = path_folder_data + "working_z_functions.pkl.gz"
        path_file_data_txt = path_folder_data + "working_z_functions.txt"

        if not os.path.exists(path_file_data):
            data = DotMap()
            data.func_str_lst = [func_str_complete]

            with gzip.open(path_file_data, "wb") as fout:
                dill.dump(data, fout)
        else:
            with gzip.open(path_file_data, "rb") as fin:
                data = dill.load(fin)

            data.func_str_lst.append(func_str_complete)

            with open(path_file_data_txt, "w") as fout:
                for line in data.func_str_lst:
                    fout.write(line + "\n")

            with gzip.open(path_file_data, "wb") as fout:
                dill.dump(data, fout)

        print("newest founded function:\n{}".format(func_str_complete))

        # print("data.func_str_lst:\n{}".format(data.func_str_lst))
        # lst = data.func_str_lst

        # print("Amount of found functions: {}".format(len(lst)))
        # for i, func_str in enumerate(lst, 1):
        #     print("\ni: {}, func_str: {}".format(i, func_str))

    def _construct_message(self):
        return "IT WORKS!"
Пример #3
0
class AnalysisHandler(object):
    """
    Hold functions to run and the parameters to use for them.

    Attributes
    ----------
    fns_to_run : list of functions
        The functions to run
    fn_param_list : list of tuples
        The arguments to pass to these functions.
        The arguments are passed in order, so these are positional.
    fn_kwargs_list : list of dicts
        Keyword arguments to pass to the functions to run.
    results : indexed.IndexedOrderedDict
        The results of the function calls
    verbose : bool
        Whether to print more information while running the functions.
    handle_errors : bool
        Whether to handle errors during runtime of underlying functions,
        or to crash on error.

    Parameters
    ----------
    verbose : bool, optional
        Sets the value of the verbose attribute, defaults to False.
    handle_errors : bool, optional
        Sets the value of the handle_errors attribute, defaults to False.

    """
    def __init__(self, verbose=False, handle_errors=False):
        """See help(AnalysisHandler)."""
        self.fns_to_run = []
        self.fn_params_list = []
        self.fn_kwargs_list = []
        self.results = IndexedOrderedDict()
        self.verbose = verbose
        self.handle_errors = handle_errors
        self._was_error = False

    def set_handle_errors(self, handle_errors):
        """Set the value of self.handle_errors."""
        self.handle_errors = handle_errors

    def set_verbose(self, verbose):
        """Set the value of self.verbose."""
        self.verbose = verbose

    def get_results(self):
        """Return the results."""
        return self.results

    def run_all(self):
        """Alias for run_all_fns."""
        self.run_all_fns()

    def run_all_fns(self, pbar=False):
        """Run all of the established functions."""
        self._was_error = False
        if pbar:
            pbar_ = tqdm(range(len(self.fns_to_run)))
            for i in pbar_:
                fn = self.fns_to_run[i]
                fn_params = self.fn_params_list[i]
                fn_kwargs = self.fn_kwargs_list[i]
                self._run_fn(fn, *fn_params, **fn_kwargs)
        else:
            fn_zipped = zip(self.fns_to_run, self.fn_params_list,
                            self.fn_kwargs_list)
            for (fn, fn_params, fn_kwargs) in fn_zipped:
                self._run_fn(fn, *fn_params, **fn_kwargs)
        if self._was_error:
            logging.warning("A handled error occurred while running analysis")
        self._was_error = False

    def reset(self):
        """Reset this object, clearing results and function list."""
        self.reset_func_list()
        self.reset_results()

    def reset_func_list(self):
        """Reset all functions and parameters."""
        self.fns_to_run = []
        self.fn_params_list = []
        self.fn_kwargs_list = []

    def reset_results(self):
        """Reset the results."""
        self.results = IndexedOrderedDict()

    def add_fn(self, fn, *args, **kwargs):
        """
        Add the function fn to the list with the given args and kwargs.

        Parameters
        ----------
        fn : function
            The function to add.
        *args : positional arguments
            The positional arguments to run the function with.
        **kwargs : keyword arguments
            The keyword arguments to run the function with.

        Returns
        -------
        None

        """
        self.fns_to_run.append(fn)
        self.fn_params_list.append(args)
        self.fn_kwargs_list.append(kwargs)

    def save_results(self, output_location):
        """
        Save the results of analysis to the given output location.
        
        Parameters
        ----------
        output_location : string
            Path to a csv to save results to.
    
        Returns
        -------
        None

        """
        with open(output_location, "w") as f:
            print("Saving results to {}".format(output_location))
            for k, v in self.results.items():
                f.write(k.replace(" ", "_").replace(",", "_") + "\n")
                o_str = save_mixed_dict_to_csv(v, None, save=False)
                f.write(o_str)

    def _run_fn(self, fn, *args, **kwargs):
        """
        Run the function with *args and **kwargs, not usually publicly called.

        Pass simuran_save_result as a keyword argument to control
        if the result of the function is saved or not.

        Parameters
        ----------
        fn : function
            The function to run.

        Returns
        -------
        object
            The return value of the function

        """
        if self.verbose:
            print("Running {} with params {} kwargs {}".format(
                fn, *args, **kwargs))
        if self.handle_errors:
            try:
                result = fn(*args, **kwargs)
            except BaseException as e:
                log_exception(
                    e,
                    "Running {} with args {} and kwargs {}".format(
                        fn.__name__, args, kwargs),
                )
                self._was_error = True
                result = "SIMURAN-ERROR"
        else:
            result = fn(*args, **kwargs)

        ctr = 1
        save_result = kwargs.get("simuran_save_result", True)
        save_name = str(fn.__name__)
        if save_result:
            while save_name in self.results.keys():
                save_name = str(fn.__name__) + "_{}".format(ctr)
                ctr = ctr + 1
            self.results[save_name] = result

        return result

    def __str__(self):
        """String representation of this class."""
        return "{} with functions:\n {}, args:\n {}, kwargs:\n {}".format(
            self.__class__.__name__,
            self.fns_to_run,
            self.fn_params_list,
            self.fn_kwargs_list,
        )
Пример #4
0
class ChurchYear(object):
    def __iter__(self):
        return ChurchYearIterator(self)

    def __init__(self, year_of_advent, calendar="ACNA_BCP2019"):

        self.calendar = Calendar.objects.filter(abbreviation=calendar).first()

        self.start_year = year_of_advent
        self.end_year = year_of_advent + 1

        self.dates = IndexedOrderedDict()

        start_date = advent(year_of_advent)
        end_date = advent(year_of_advent + 1) - timedelta(days=1)

        self.start_date = start_date
        self.end_date = end_date

        self.seasons = self._get_seasons()
        self.season_tracker = None
        # create each date
        for single_date in self.daterange(start_date, end_date):
            name = single_date.strftime("%Y-%m-%d")
            self.dates[name] = CalendarDate(single_date,
                                            calendar=self.calendar,
                                            year=self)

        # add commemorations to date
        commemorations = (Commemoration.objects.select_related(
            "rank", "cannot_occur_after__rank").filter(
                calendar__abbreviation=calendar).all())
        already_added = []
        for commemoration in commemorations:

            if not commemoration.can_occur_in_year(self.start_year):
                continue

            try:
                self.dates[commemoration.initial_date_string(
                    self.start_year)].add_commemoration(commemoration)
                already_added.append(commemoration.pk)

            except KeyError:
                pass

        for key, calendar_date in self.dates.items():

            # seasons
            self._set_season(calendar_date)

            # apply transfers
            transfers = calendar_date.apply_rules()
            new_date = (calendar_date.date +
                        timedelta(days=1)).strftime("%Y-%m-%d")
            if new_date in self.dates.keys():
                self.dates[new_date].required = transfers + self.dates[
                    new_date].required

        SetNamesAndCollects(self)

        # print(
        #     "{} = {} - {} {}".format(
        #         calendar_date.season,
        #         calendar_date.date.strftime("%a, %b, %d, %Y"),
        #         calendar_date.primary.__repr__(),
        #         "(Proper {})".format(calendar_date.proper.number) if calendar_date.proper else "",
        #         "+" if calendar_date.day_of_special_commemoration else "",
        #     )
        #
        # )
        # print(calendar_date.required, calendar_date.optional)

        # #print("{} - {} - {}".format(self.mass_year, sself.daily_mass_year, self.office_year))

    def _get_seasons(self):
        seasons = (Season.objects.filter(calendar=Calendar.objects.filter(
            abbreviation=self.calendar.abbreviation).get()).order_by(
                "order").all())
        season_mapping = {}
        for season in seasons:
            season_mapping[season.start_commemoration.name] = season
        self.seasons = season_mapping
        # print(self.seasons)

    def _set_season(self, calendar_date):

        calendar_date.season = self.season_tracker
        calendar_date.evening_season = calendar_date.season

        if not calendar_date.required:
            return

        possible_days = [feast.name for feast in calendar_date.required]

        if not self.seasons:
            self._get_seasons()

        if "The Day of Pentecost" in possible_days:
            calendar_date.season = self.season_tracker

        for match in self.seasons.keys():
            if match in possible_days:
                self.season_tracker = self.seasons[match]
                if "The Day of Pentecost" not in possible_days:
                    calendar_date.season = self.season_tracker

        calendar_date.evening_season = calendar_date.season

    @staticmethod
    def daterange(start_date, end_date):
        for n in range(int((end_date - start_date).days + 1)):
            yield start_date + timedelta(n)

    @cached_property
    def mass_year(self):

        if self.start_year % 3 == 0:
            return "A"

        if self.start_year % 3 == 1:
            return "B"

        if self.start_year % 3 == 2:
            return "C"

    @cached_property
    def daily_mass_year(self):

        return 1 if self.end_year % 2 != 0 else 2

    @cached_property
    def office_year(self):

        return "I" if self.start_year % 2 == 0 else "II"

    @cached_property
    def first_date(self):
        return self.dates[:1]

    @cached_property
    def last_date(self):
        return self.dates[-1]

    def get_date(self, date_string):
        date = to_date(date_string)
        try:
            date = self.dates[date.strftime("%Y-%m-%d")]
            date.year = self
            return date
        except KeyError:
            print(date)
            print(date.strftime("%Y-%m-%d"))
            return None