예제 #1
0
    def __init__(self, radius_mode=False, specsheet=None, **kwargs):
        self.ro_version = rayoptics.__version__
        self.radius_mode = radius_mode
        self.specsheet = specsheet
        self.system_spec = SystemSpec()
        self.seq_model = SequentialModel(self, **kwargs)
        self.optical_spec = OpticalSpecs(self, **kwargs)
        self.parax_model = ParaxialModel(self, **kwargs)
        self.ele_model = ElementModel(self, **kwargs)

        if self.specsheet:
            self.set_from_specsheet()
예제 #2
0
    def __init__(self, radius_mode=False, specsheet=None, **kwargs):
        self.ro_version = rayoptics.__version__
        self.radius_mode = radius_mode
        self.specsheet = specsheet
        self.system_spec = SystemSpec()
        self.seq_model = SequentialModel(self, **kwargs)
        self.optical_spec = OpticalSpecs(self, **kwargs)
        self.parax_model = ParaxialModel(self, **kwargs)
        self.ele_model = ElementModel(self, **kwargs)

        if self.specsheet:
            self.set_from_specsheet()

        if kwargs.get('do_init', True):
            # need to do this after OpticalSpec is initialized
            self.seq_model.update_model()
예제 #3
0
class OpticalModel:
    """ Top level container for optical model.

    The OpticalModel serves as a top level container of model properties.
    Key aspects are built-in element and surface based repesentations of the
    optical surfaces.
    A sequential optical model is a sequence of surfaces and gaps.
    Additionally, it includes optical usage information to specify the
    aperture, field of view, spectrum and focus.

    Attributes:
        ro_version: current version of rayoptics
        radius_mode: if True output radius, else output curvature
        specsheet: :class:`~rayoptics.parax.specsheet.SpecSheet`
        system_spec: :class:`.SystemSpec`
        seq_model: :class:`~rayoptics.seq.sequential.SequentialModel`
        optical_spec: :class:`~rayoptics.raytr.opticalspec.OpticalSpecs`
        parax_model: :class:`~rayoptics.parax.paraxialdesign.ParaxialModel`
        ele_model: :class:`~rayoptics.elem.elements.ElementModel`
    """

    def __init__(self, radius_mode=False, specsheet=None, **kwargs):
        self.ro_version = rayoptics.__version__
        self.radius_mode = radius_mode
        
        self.specsheet = specsheet
        self.system_spec = SystemSpec()
        self.seq_model = SequentialModel(self, **kwargs)
        self.optical_spec = OpticalSpecs(self, specsheet=specsheet, **kwargs)
        self.parax_model = ParaxialModel(self, **kwargs)
        self.ele_model = ElementModel(self, **kwargs)
        self.part_tree = PartTree(self, **kwargs)

        self.map_submodels()

        if self.specsheet:
            self.set_from_specsheet()

        if kwargs.get('do_init', True):
            # need to do this after OpticalSpec is initialized
            self.seq_model.update_model()
            elements_from_sequence(self.ele_model,
                                   self.seq_model,
                                   self.part_tree)

    def map_submodels(self):
        """Setup machinery for model mapping api. """
        submodels = {}
        submodels['specsheet'] = self.specsheet
        submodels['system_spec'] = self.system_spec
        submodels['seq_model'] = self.seq_model
        submodels['optical_spec'] = self.optical_spec
        submodels['parax_model'] = self.parax_model
        submodels['ele_model'] = self.ele_model
        submodels['part_tree'] = self.part_tree
        # Add a level of indirection to allow short and long aliases
        submodel_aliases = {
            'ss': 'specsheet', 'specsheet': 'specsheet',
            'sys': 'system_spec', 'system_spec': 'system_spec',
            'sm': 'seq_model', 'seq_model': 'seq_model',
            'osp': 'optical_spec', 'optical_spec': 'optical_spec',
            'pm': 'parax_model', 'parax_model': 'parax_model',
            'em': 'ele_model', 'ele_model': 'ele_model',
            'pt': 'part_tree', 'part_tree': 'part_tree',
            }
        self._submodels = submodels, submodel_aliases

    def __getitem__(self, key):
        """ Provide mapping interface to submodels. """
        submodels, submodel_aliases = self._submodels
        return submodels[submodel_aliases[key]]

    def name(self):
        return self.system_spec.title

    def reset(self):
        rdm = self.radius_mode
        self.__init__()
        self.radius_mode = rdm

    def __json_encode__(self):
        attrs = dict(vars(self))
        if hasattr(self, 'app_manager'):
            del attrs['app_manager']
        del attrs['_submodels']
        return attrs

    def listobj_str(self):
        vs = vars(self)
        o_str = f"{type(self).__name__}:\n"
        for k, v in vs.items():
             o_str += f"{k}: {v}\n"
        return o_str

    def set_from_specsheet(self, specsheet=None):
        if specsheet:
            self.specsheet = specsheet
        else:
            specsheet = self.specsheet
        self.optical_spec.set_from_specsheet(specsheet)
        self.seq_model.set_from_specsheet(specsheet)

    def save_model(self, file_name, version=None):
        """Save the optical_model in a ray-optics JSON file.
        
        Args:
            file_name: str or Path
            version: optional override for rayoptics version number
        """
        file_extension = os.path.splitext(file_name)[1]
        filename = file_name if len(file_extension) > 0 else file_name+'.roa'
        # update version number prior to writing file.
        self.ro_version = rayoptics.__version__ if version is None else version

        fs_dict = {}
        fs_dict['optical_model'] = self
        with open(filename, 'w') as f:
            json_tricks.dump(fs_dict, f, indent=1,
                             separators=(',', ':'), allow_nan=True)

    def sync_to_restore(self):
        if not hasattr(self, 'ro_version'):
            self.ro_version = rayoptics.__version__

        self.seq_model.sync_to_restore(self)
        self.ele_model.sync_to_restore(self)
        self.optical_spec.sync_to_restore(self)

        if hasattr(self, 'parax_model'):
            self.parax_model.sync_to_restore(self)
        else:
            self.parax_model = ParaxialModel(self)

        if hasattr(self, 'specsheet'):
            self.specsheet.sync_to_restore(self)
        else:
            self.specsheet = None

        if hasattr(self, 'part_tree'):
            self.part_tree.sync_to_restore(self)
        else:
            self.part_tree = PartTree(self)
            self.part_tree.add_element_model_to_tree(self.ele_model)

        self.map_submodels()

        self.update_model()

    def update_model(self, **kwargs):
        self.seq_model.update_model(**kwargs)
        self.optical_spec.update_model(**kwargs)
        self.parax_model.update_model(**kwargs)
        self.ele_model.update_model(**kwargs)
        self.part_tree.update_model(**kwargs)
        if self.specsheet is None:
            self.specsheet = create_specsheet_from_model(self)
            self.map_submodels()

    def nm_to_sys_units(self, nm):
        """ convert nm to system units

        Args:
            nm (float): value in nm

        Returns:
            float: value converted to system units
        """
        return self.system_spec.nm_to_sys_units(nm)

    def add_lens(self, **kwargs):
        descriptor = ele.create_lens(**kwargs)
        kwargs['insert'] = True
        self.insert_ifc_gp_ele(*descriptor, **kwargs)

    def add_mirror(self, **kwargs):
        descriptor = ele.create_mirror(**kwargs)
        kwargs['insert'] = True
        self.insert_ifc_gp_ele(*descriptor, **kwargs)

    def add_thinlens(self, **kwargs):
        descriptor = ele.create_thinlens(**kwargs)
        kwargs['insert'] = True
        self.insert_ifc_gp_ele(*descriptor, **kwargs)

    def add_dummy_plane(self, **kwargs):
        descriptor = ele.create_dummy_plane(**kwargs)
        kwargs['insert'] = True
        self.insert_ifc_gp_ele(*descriptor, **kwargs)

    def add_from_file(self, filename, **kwargs):
        descriptor = ele.create_from_file(filename, **kwargs)
        kwargs['insert'] = True
        self.insert_ifc_gp_ele(*descriptor, **kwargs)

    def insert_ifc_gp_ele(self, *descriptor, **kwargs):
        """ insert interfaces and gaps into seq_model and eles into ele_model

        Args:
            descriptor: a tuple of additions for the sequential, element and
                        part tree models
            kwargs: keyword arguments including
                idx: insertion point in the sequential model
                insert: if True, insert the chunk, otherwise replace it
                t: the thickness following a chuck when inserting
        """
        sm = self['seq_model']
        seq, elm, e_node = descriptor
        if 'idx' in kwargs:
            sm.cur_surface = kwargs['idx']
        idx = sm.cur_surface

        e_node.parent = self.part_tree.root_node

        # distinguish between adding a new chunk, which requires splitting a
        #  gap in two, and replacing a node, which uses the existing gaps.
        ins_prev_gap = False
        if 'insert' in kwargs:
            t_after = kwargs['t'] if 't' in kwargs else 0.
            if sm.get_num_surfaces() == 2:
                # only object space gap, add image space gap following this
                gap_label = "Image space"
                ins_prev_gap = False
            else:
                # we have both object and image space gaps; retain the image
                # space gap by splitting and inserting the new gap before the
                # inserted chunk, unless we're inserting before idx=1.
                gap_label = None
                if idx > 0:
                    ins_prev_gap = True

            if ins_prev_gap:
                t_air, sm.gaps[idx].thi = sm.gaps[idx].thi, t_after
            else:
                t_air = t_after
            g, ag, ag_node = ele.create_air_gap(t=t_air, label=gap_label)
            if not ins_prev_gap:
                seq[-1][mc.Gap] = g
            elm.append(ag)
            ag_node.parent = self.part_tree.root_node
        else:
            # replacing an existing node. need to hook new chunk final
            # interface to the existing gap and following (air gap) element
            g = sm.gaps[sm.cur_surface+1]
            seq[-1][mc.Gap] = g
            ag, ag_node = self.part_tree.parent_object(g, '#airgap')            # ag.idx = seq[-1][mc.Intfc]

        for sg in seq:
            if ins_prev_gap:
                gap, g = g, sg[mc.Gap]
            else:
                gap = sg[mc.Gap]
            sm.insert(sg[mc.Intfc], gap, prev=ins_prev_gap)

        for e in elm:
            self.ele_model.add_element(e)
        self.ele_model.sequence_elements()

    def remove_ifc_gp_ele(self, *descriptor, **kwargs):
        """ remove interfaces and gaps from seq_model and eles from ele_model
        """
        seq, elm, e_node = descriptor
        sg = seq[0]
        idx = self.seq_model.ifcs.index(sg[mc.Intfc])

        # verify that the sequences match
        seq_match = True
        for i, sg in enumerate(seq):
            if sg[0] is not self.seq_model.ifcs[idx+i]:
                seq_match = False
                break

        if seq_match:
            # remove interfaces in reverse
            for i in range(idx+len(seq)-1, idx-1, -1):
                self.seq_model.remove(i)

        for e in elm:
            self.ele_model.remove_element(e)

        e_node.parent = None

    def remove_node(self, e_node):
        # remove interfaces from seq_model
        self.seq_model.remove_node(e_node)
        # remove elements from ele_model
        self.ele_model.remove_node(e_node)
        # unhook node
        e_node.parent = None

    def rebuild_from_seq(self):
        """ Rebuild ele_model and part_tree from seq_model. """
        self['em'].elements = []
        self['pt'].root_node.children = []
        elements_from_sequence(self['em'], self['sm'], self['pt'])
예제 #4
0
class OpticalModel:
    """ Top level container for optical model.

    The OpticalModel serves as a top level container of model properties.
    Key aspects are built-in element and surface based repesentations of the
    optical surfaces.
    A sequential optical model is a sequence of surfaces and gaps.
    Additionally, it includes optical usage information to specify the
    aperture, field of view, spectrum and focus.

    Attributes:
        ro_version: current version of rayoptics
        radius_mode: if True output radius, else output curvature
        specsheet: :class:`~rayoptics.parax.specsheet.SpecSheet`
        system_spec: :class:`.SystemSpec`
        seq_model: :class:`~rayoptics.seq.sequential.SequentialModel`
        optical_spec: :class:`~rayoptics.raytr.opticalspec.OpticalSpecs`
        parax_model: :class:`~rayoptics.parax.paraxialdesign.ParaxialModel`
        ele_model: :class:`~rayoptics.elem.elements.ElementModel`
    """
    def __init__(self, radius_mode=False, specsheet=None, **kwargs):
        self.ro_version = rayoptics.__version__
        self.radius_mode = radius_mode
        self.specsheet = specsheet
        self.system_spec = SystemSpec()
        self.seq_model = SequentialModel(self, **kwargs)
        self.optical_spec = OpticalSpecs(self, **kwargs)
        self.parax_model = ParaxialModel(self, **kwargs)
        self.ele_model = ElementModel(self, **kwargs)

        if self.specsheet:
            self.set_from_specsheet()

    def name(self):
        return self.system_spec.title

    def reset(self):
        rdm = self.radius_mode
        self.__init__()
        self.radius_mode = rdm

    def __json_encode__(self):
        # update version number prior to writing file.
        self.ro_version = rayoptics.__version__
        attrs = dict(vars(self))
        if hasattr(self, 'app_manager'):
            del attrs['app_manager']
        return attrs

    def set_from_specsheet(self, specsheet=None):
        if specsheet:
            self.specsheet = specsheet
        else:
            specsheet = self.specsheet
        self.seq_model.set_from_specsheet(specsheet)
        self.optical_spec.set_from_specsheet(specsheet)

    def save_model(self, file_name):
        file_extension = os.path.splitext(file_name)[1]
        filename = file_name if len(file_extension) > 0 else file_name + '.roa'
        fs_dict = {}
        fs_dict['optical_model'] = self
        with open(filename, 'w') as f:
            json_tricks.dump(fs_dict,
                             f,
                             indent=1,
                             separators=(',', ':'),
                             allow_nan=True)

    def sync_to_restore(self):
        if not hasattr(self, 'ro_version'):
            self.ro_version = rayoptics.__version__

        self.seq_model.sync_to_restore(self)
        self.ele_model.sync_to_restore(self)
        self.optical_spec.sync_to_restore(self)

        if hasattr(self, 'parax_model'):
            self.parax_model.sync_to_restore(self)
        else:
            self.parax_model = ParaxialModel(self)

        if hasattr(self, 'specsheet'):
            self.specsheet.sync_to_restore(self)
        else:
            self.specsheet = None

        self.update_model()

    def update_model(self):
        self.seq_model.update_model()
        self.optical_spec.update_model()
        self.parax_model.update_model()
        self.ele_model.update_model()
        if self.specsheet is None:
            self.specsheet = create_specsheet_from_model(self)

    def nm_to_sys_units(self, nm):
        """ convert nm to system units

        Args:
            nm (float): value in nm

        Returns:
            float: value converted to system units
        """
        return self.system_spec.nm_to_sys_units(nm)

    def add_lens(self, **kwargs):
        seq, elm = ele.create_lens(**kwargs)
        kwargs['insert'] = True
        self.insert_ifc_gp_ele(seq, elm, **kwargs)

    def add_mirror(self, **kwargs):
        seq, elm = ele.create_mirror(**kwargs)
        kwargs['insert'] = True
        self.insert_ifc_gp_ele(seq, elm, **kwargs)

    def add_thinlens(self, **kwargs):
        seq, elm = ele.create_thinlens(**kwargs)
        kwargs['insert'] = True
        self.insert_ifc_gp_ele(seq, elm, **kwargs)

    def add_dummy_plane(self, **kwargs):
        seq, elm = ele.create_dummy_plane(**kwargs)
        kwargs['insert'] = True
        self.insert_ifc_gp_ele(seq, elm, **kwargs)

    def add_from_file(self, filename, **kwargs):
        seq, elm = ele.create_from_file(filename, **kwargs)
        kwargs['insert'] = True
        self.insert_ifc_gp_ele(seq, elm, **kwargs)

    def insert_ifc_gp_ele(self, *args, **kwargs):
        """ insert interfaces and gaps into seq_model and eles into ele_model
        """
        seq, elm = args
        if 'idx' in kwargs:
            self.seq_model.cur_surface = kwargs['idx']

        # distinguish between adding a new chunk, which requires splitting a
        #  gap in two, and replacing a node, which uses the existing gaps.
        if 'insert' in kwargs:
            t = kwargs['t'] if 't' in kwargs else 0.
            g, ag = ele.create_air_gap(t=t, ref_ifc=seq[-1][mc.Intfc])
            seq[-1][mc.Gap] = g
            elm.append(ag)
        else:
            # replacing an existing node. need to hook new chunk final
            # interface to the existing gap and following (air gap) element
            g = self.seq_model.gaps[self.seq_model.cur_surface + 1]
            seq[-1][mc.Gap] = g
            ag = self.ele_model.gap_dict[g]
            ag.ref_ifc = seq[-1][mc.Intfc]  # tacit assumption is ag == AirGap

        for sg in seq:
            self.seq_model.insert(sg[mc.Intfc], sg[mc.Gap])

        for e in elm:
            self.ele_model.add_element(e)
        self.ele_model.sequence_elements()

    def remove_ifc_gp_ele(self, *args, **kwargs):
        """ remove interfaces and gaps from seq_model and eles from ele_model
        """
        seq, elm = args
        sg = seq[0]
        idx = self.seq_model.ifcs.index(sg[mc.Intfc])

        # verify that the sequences match
        seq_match = True
        for i, sg in enumerate(seq):
            if sg[0] is not self.seq_model.ifcs[idx + i]:
                seq_match = False
                break

        if seq_match:
            # remove interfaces in reverse
            for i in range(idx + len(seq) - 1, idx - 1, -1):
                self.seq_model.remove(i)

        for e in elm:
            self.ele_model.remove_element(e)