########################################################################## ###### File version: 1.2 ##### ###### ##### ###### TDR user-defined helper functions acting as wrappers around ##### ###### TDR built-in script functions to increase user-friendliness. ##### ###### ##### ###### Product version requirements for available wrapper functions: ##### ###### ##### ###### tdrinfoString - 2026 R1 ##### ###### addGeometry - 2026 R1 ##### ###### tdrWriteOpticalGeneration - 2026 R1 ##### ###### tdrImportDoping - 2026 R1.2 ##### ###### tdrImportNPDensity - 2026 R1.2 ##### ###### ##### ########################################################################## # Returns true if x is a scalar number or multidimensional vector. # Otherwise returns false. function isnumber(x) { # Attempt to perform a numerical operation try { # Try to add zero to the variable test = x + 0; # If successful, it's a number if (length(x) == 1) { #"The variable is a scalar number."; return true; } else { #"The variable is an array/vector."; return true; } } catch(errMsg); if(errMsg != ""){ # If an error occurs, it's likely a string return false; } } function dimSubstring(inStr) { # Find the position of the dimension substring pos = findstring(inStr, "3d"); if (pos == -1) { pos = findstring(inStr, "2d"); } # Extract the dimension substring dimension = ""; if (pos != -1) { dimension = substring(inStr, pos, 2); } # Output the result return dimension; } # Returns a formatted string with the full content of the tdr file convenient for displaying # in the script prompt or saving to a text file for viewing with a text editor. # x - The output of tdrinfo script command. # Note: Non-recursive version based on LIFO stack implementation. function tdrinfoString(x) { maxLevelDepth = 20; formattedString = ""; # Stack of tasks: # kind = 0 -> process value (determine type and push children) # kind = 1 -> print header string only (with current level) stack_nodes = cell(1); # holds node for kind=0, unused for kind=1 stack_nodes.pop; stack_kinds = cell(1); # numeric array: 0 or 1 stack_kinds.pop; stack_levels = cell(1); # numeric array: level per task stack_levels.pop; stack_headers = cell(1); # holds header strings for kind=1, empty for kind=0 stack_headers.pop; # initial task: process the root value n = length(stack_kinds) + 1; stack_nodes.insert(n,x); stack_kinds.insert(n,0); stack_levels.insert(n,0); stack_headers.insert(n,""); # process until the stack is empty for (0;length(stack_kinds) > 0;0) { # pop last task (LIFO) kind = stack_kinds.pop; lvl = stack_levels.pop; node = stack_nodes.pop; header= stack_headers.pop; # enforce recursion-equivalent depth limit if (lvl > maxLevelDepth) { print("Recursion level exceeded " + num2str(maxLevelDepth) + "."); break; } # compute indentation for current level indent = ""; for (ii = 1:4*lvl) { indent = indent + " "; } if (kind == 1) { # header-only task: print precomputed header at this level formattedString = formattedString + indent + header + "\n"; } else { # kind == 0: process value "node" if (isnumber(node)) { formattedString = formattedString + indent + "Number:"; if(size(node,1) == 1) { formattedString = formattedString + " " + num2str(node,"%f") + "\n"; } else { for(nodeRow = 1:size(node,1)) { if(nodeRow == 1) { formattedString = formattedString + " " + num2str(node(nodeRow,:),"%f") + "\n"; } else { formattedString = formattedString + indent + " " + num2str(node(nodeRow,:),"%f") + "\n"; } } } } else if (iscell(node)) { # iterate children, but push in reverse order so that the first child # is processed next (depth-first, same as recursion) N = length(node); for (ii = N:-1:1) { # push child processing (level+1) first if (lvl + 1 > maxLevelDepth) { print("Recursion level exceeded " + num2str(maxLevelDepth) + "."); break; } n = length(stack_kinds) + 1; stack_nodes.insert(n,node{ii}); stack_kinds.insert(n,0); stack_levels.insert(n,lvl+1); stack_headers.insert(n,""); # then push the child's header at current level n = length(stack_kinds) + 1; stack_nodes.insert(n,0); stack_kinds.insert(n,1); stack_levels.insert(n,lvl); stack_headers.insert(n,"Cell: " + num2str(ii)); } } else if (isstruct(node)) { # get field names as list fields = splitstring(getfield(node), "\n"); Nf = length(fields); # push children in reverse order to preserve recursion order for (ii = Nf:-1:1) { fname = fields{ii}; fval = getfield(node, fname); # child processing task (level+1) if (lvl + 1 > maxLevelDepth) { print("Recursion level exceeded " + num2str(maxLevelDepth) + "."); break; } n = length(stack_kinds) + 1; stack_nodes.insert(n,fval); stack_kinds.insert(n,0); stack_levels.insert(n,lvl+1); stack_headers.insert(n,""); # header task at current level n = length(stack_kinds) + 1; stack_nodes.insert(n,0); stack_kinds.insert(n,1); stack_levels.insert(n,lvl); stack_headers.insert(n,"Field: " + fname); } } else { # treat as string (matches original behavior) formattedString = formattedString + indent + "String: " + node + "\n"; } } } return formattedString; } # Same as tdrinfoString but implemented with recursion. # x - The output of tdrinfo script command. # level - Recursion depth and should be 0 when the user calls the function. # Note: The script needs to be executed twice for it to start working properly. # Gives the same output as the nonrecursive implementation, but not recommended # due to the above mentioned issue. function tdrinfoString2(x, level) { maxLevelDepth = 20; formattedString = ""; # Check if the recursion level exceeds maxLevelDepth if (level > maxLevelDepth) { print("Recursion level exceeded " + num2str(maxLevelDepth) + "."); break; } # Determine the indentation based on the recursion level indent = ""; for(i = 1:4*level) { indent = indent + " "; } # Check if the variable is a number if (isnumber(x)) { formattedString = formattedString + indent + "Number: " + num2str(x) + "\n"; } else if (iscell(x)) { # Iterate through the cell for (i = 1:length(x)) { formattedString = formattedString + indent + "Cell: " + num2str(i) + "\n"; formattedString = formattedString + tdrinfoString2(x{i}, level + 1); } } else if (isstruct(x)) { # Get fields of the struct fields = splitstring(getfield(x), "\n"); for (i = 1:length(fields)) { formattedString = formattedString + indent + "Field: " + fields{i} + "\n"; formattedString = formattedString + tdrinfoString2(getfield(x,fields{i}), level + 1); } } else { # Assume it's a string if not a number, cell, or struct formattedString = formattedString + indent + "String: " + x + "\n"; } return formattedString; } # Go over all regions in one geometry and add them to the object tree. Only regions of the same dimensionality # as geometry are added. Uses tdraddregion to add geometry objects. # geomName: Name of geometry to add from the tdrinfo output. # materialNames: Struct that represents a map from the tdr material (output of tdrinfo) to the Lumerical material names. # If the struct does not have a tdr material name, or if a Lumerical material name is an empty string it is ignored. # Can be set later with set or setnamed script commands. # destCsType: Destination (Lumerical CAD) coordinate system type. # Returns the output of tdrinfo script command. # Note: material with the same name must exist in our database. function addGeometry(tdrFileName, geomName, materialNames, destCsType) { try { resTdrInfo = tdrinfo(tdrFileName); #built-in script command } catch(err); if(err != "") { return err; } geomIndex = 0; geomFound = false; for(i=1; i<=length(resTdrInfo.Geometries) and !geomFound; i=i+1) { if(resTdrInfo.Geometries{i}.Name == geomName) { geomIndex = i; geomFound = true; } } if(!geomFound) { return "Geometry " + geomName + " not found in file " + tdrFileName + "."; } numRegions = length(resTdrInfo.Geometries{geomIndex}.Regions); geomName = resTdrInfo.Geometries{geomIndex}.Name; geomType = resTdrInfo.Geometries{geomIndex}.GeometryType; regionsAdded = "Regions added:\n"; for(j=1:numRegions) { regType = resTdrInfo.Geometries{geomIndex}.Regions{j}.Type; if(findstring(regType,dimSubstring(geomType)) != -1) {#only regions of same dimension as geometry regName = resTdrInfo.Geometries{geomIndex}.Regions{j}.Name; regMat = resTdrInfo.Geometries{geomIndex}.Regions{j}.Material; optStruct = struct; optStruct = setfield(optStruct,"destination_cs",destCsType); if(isfield(materialNames,regMat)) {#if tdr material name is provided in map if(getfield(materialNames,regMat) != "") {#if Lumerical material name is not empty string optStruct.material = getfield(materialNames,regMat); } } err = ""; try { tdraddregion(tdrFileName,geomName+":"+regName,optStruct); #built-in script command } catch(err); if(err != "") { return err; } regionsAdded = regionsAdded + " " + regName + "\n"; } } res = struct; res.tdrinfo = resTdrInfo; res.regionsAdded = regionsAdded; return res; } # Specialization of tdrwritedataset for the FDTD optical generation dataset. Expects the same name and units for the # optical generation attribute as provided by the CW generation analysis object. # OG: Optical generation dataset from the CW generation analysis object. # opt: struct with fields region_name - arbitrary name of the geometry region in tdr file, # source_cs - Lumerical coordinate system (shoud be the same one used for adding geometry), # destination_cs - coordinate system for data saved to tdr file (should be the same as in the # tdr file used for importing geometry), # memory_layout (optional) - "x-fastest" (default) or "z-fastest". function tdrWriteOpticalGeneration(tdrFileName,OG,opt) { # Validate that OG contains G attribute (as in default CW generation analysis object) try { attrList = getattribute(OG); } catch(errMsg); if(errMsg != "") { return errMsg; } attrList = splitstring(attrList,"\n"); foundG = false; for(i=1:length(attrList)) { if(attrList{i} == "G") { foundG = true; } } if(!foundG) { return "tdrWriteOpticalGeneration: There is no G attribute in the input optical generation dataset."; } # Convert OG.G from m to cm units for Sentaurus OGcm = rectilineardataset("OG",OG.x,OG.y,OG.z); OGcm.addattribute("G",OG.G*1e-6); # Set additional input options opt = setfield(opt,"G_unit","cm^-3*s^-1"); #optional (if unitless, but otherwise should be specified) opt = setfield(opt,"G_rename","OpticalGeneration"); #optional (but S-Device expects exactly this name) try { tdrwritedataset(tdrFileName,OGcm,opt); } catch(errMsg); if(errMsg != "") { return errMsg; } return ""; #return empty string on success } # Specialization of tdrimportdataset for doping concentrations. # datasets - Cell of doping dataset names over different regions for one doping type in format "state name/quantity name/region name". # type - Type of doping for datasets (all datasets must be the same type). # destination_cs - Lumerical ccordinate system (should be the same as used in geometry import). function tdrImportDoping(tdrFileName,datasets,type,destination_cs) { if(!iscell(datasets)) { return "datasets input parameter must be a cell that goes over regions for one doping type."; } for(i=1:length(datasets)) { subStrs = splitstring(datasets{i},"/"); if(length(subStrs) != 3) { return datasets{i} + " must contain 3 substrings split by / character. " + "First substring is state name, second is quantity name, " + "and third is region name."; } opt = struct; opt = setfield(opt,subStrs{2} + "_rename","N"); #always must be N opt = setfield(opt,subStrs{2} + "_target_prefixed_units","m"); opt = setfield(opt,"lum_dataset_name","doping_" + subStrs{3} + "_" + type); opt = setfield(opt,"destination_cs",destination_cs); # First delete existing doping object of the same name previousScope = groupscope; groupscope("::model::CHARGE"); objList = getobjectlist(); for(j=1:length(objList)) { if(findstring(objList{j},opt.lum_dataset_name) > 0) { select(objList{j}); delete; } } groupscope(previousScope); # Then add it again. addimportdope; set("name",opt.lum_dataset_name); set("volume type","solid"); set("volume solid",subStrs{3}); set("dopant type",type); try { retVal = tdrimportdataset(tdrFileName,datasets{i},opt); #built-in script command } catch(err); if(err != "") { return "Error occurred in importing " + datasets{i} + ": " + err; } if(!retVal) { return "Error occurred in importing " + datasets{i} + "."; } } return ""; #return empty string on success } # Specialization of tdrimportdataset for electron and hole densities. # datasets - Cell of dataset names in format "state name/quantity name/region name". # This is a nested cell. The inner cell is size two and contains electron density (first) # and hole density (second) datasets for one region. The outer cell goes over different regions. # destination_cs - Lumerical ccordinate system (should be the same as used in geometry import). function tdrImportNPDensity(tdrFileName,datasets,destination_cs) { datasetsSanityCheck = true; if(!iscell(datasets)) { datasetsSanityCheck = false; } else if(!iscell(datasets{1})) { datasetsSanityCheck = false; } if(!datasetsSanityCheck) { return "datasets input parameter must be a cell of cells, where the inner cell of size two goes over " + "electron density (first) and hole density (second) for one region and the outer cell goes over regions."; } for(i=1:length(datasets)) { opt = struct; regionDatasets = datasets{i}; if(length(regionDatasets) != 2) { return "datasets input parameter must be a cell of cells, where the inner cell of size two goes over " + "electron density (first) and hole density (second) for one region and the outer cell goes over regions."; } # Remove "none" datasets. regionDatasetsStripped = cell(1); regionDatasetsStripped.clear; typeDatasetsStripped = cell(1); typeDatasetsStripped.clear; for(j=1:length(regionDatasets)) { if(regionDatasets{j} != "none") { regionDatasetsStripped.append(regionDatasets{j}); if(j == 1) { typeDatasetsStripped.append("n"); } else if(j == 2) { typeDatasetsStripped.append("p"); } else { return "datasets input parameter must be a cell of cells, where the inner cell of size two goes over " + "electron density (first) and hole density (second) for one region and the outer cell goes over regions."; } } } for(j=1:length(regionDatasetsStripped)) { subStrs = splitstring(regionDatasetsStripped{j},"/"); if(length(subStrs) != 3) { return regionDatasetsStripped{j} + " must contain 3 substrings split by / character. " + "First substring is state name, second is quantity name, and third is region name."; } if(j > 1) { # previous region was not none, then we can compare if(region != subStrs{3}) { return "The datasets in the inner cells of datasets input parameter must apply to the same region (third substring)."; } } else { region = subStrs{3}; } opt = setfield(opt,subStrs{2} + "_rename",typeDatasetsStripped{j}); #must be n for electon and p for hole opt = setfield(opt,subStrs{2} + "_target_prefixed_units","cm"); } opt = setfield(opt,"lum_dataset_name","charge_" + region); opt = setfield(opt,"destination_cs",destination_cs); # First delete existing np density object of the same name. # We add objects to group to be able to access the list of objects later. previousScope = groupscope; try { groupscope("::model::np density"); } catch(errMsg); if(errMsg != "") { # add new group addgroup; set("name","np density"); } else { # delete if object exists in group objList = getobjectlist(); for(j=1:length(objList)) { if(findstring(objList{j},opt.lum_dataset_name) > 0) { select(objList{j}); delete; } } } groupscope(previousScope); # Then add new object to group. addgridattribute("np density"); set("name",opt.lum_dataset_name); addtogroup("np density"); try { retVal = tdrimportdataset(tdrFileName,regionDatasetsStripped,opt); #built-in script command } catch(err); errMsgSuffix = ""; for(j = 1:length(regionDatasets)) { errMsgSuffix = errMsgSuffix + regionDatasets{j}; if(j < length(regionDatasets)) { errMsgSuffix = errMsgSuffix + " and "; } } if(err != "") { return "Error occurred in importing " + errMsgSuffix + ": " + err; } if(!retVal) { return "Error occurred in importing " + errMsgSuffix + "."; } } return ""; #return empty string on success }