gdsfactory / gdsfactory

python library to design chips (Photonics, Analog, Quantum, MEMs, ...), objects for 3D printing or PCBs.
https://gdsfactory.github.io/gdsfactory/
MIT License
466 stars 196 forks source link

support different port naming conventions #233

Closed joamatab closed 1 year ago

joamatab commented 2 years ago

some functions, like meep and tidy3d write sparameters assume the o1, o2, o3 ... port names.it would be great to enable other port naming conventions.

Here are some other port conventions

joamatab commented 1 year ago

How can we make sure that any port convention works for 3D FDTD plugins?

https://github.com/gdsfactory/gdsfactory/discussions/502

import gdsfactory as gf

if __name__ == "__main__":
    import gdsfactory.simulation.gtidy3d as gt

    c = gf.components.straight(length=2)
    c.unlock()
    c.auto_rename_ports_layer_orientation()
    gt.write_sparameters(c, run=False)

@momchil-flex @flaport @simbilod @HelgeGehring

HelgeGehring commented 1 year ago

I'd guess if we use a dict for the s-parameters, which uses the port names the problem should be solved :)

simbilod commented 1 year ago

As long as the port names are associated to port objects containing the relevant properties (midpoint, orientation, width, layer, mode_cutoff, and mode_symmetry), we have everything we need to generate logical ports for FDTD.

I don't think we should store information about the port in its name string. A user could choose to also do that if they want e.g. with direction as E0, W0, but it should not be required

joamatab commented 1 year ago

This format could solve the issues

wavelength |port_in | port_out | mode_in | mode_out |   magnitude  |   phase
           |        |          |   0     |    0     |              |
           |        |          |         |          |              |
           |        |          |         |          |              |

to query s12m and s12a we can write some convenience functions for each port naming convention

df.query("port_in=='o1' & port_out=='o2' ")

@flaport

joamatab commented 1 year ago

How to internally represent Sparameters. Right now in CSV files.

TODO:

Convertor will allow you to convert old simulations to the new format

joamatab commented 1 year ago
wavelength |port_in | port_out | mode_in | mode_out |   magnitude  |   phase
           |        |          |   0     |    0     |              |
           |        |          |         |          |              |
           |        |          |         |          |              |

How about this?

d = hdf() d['wavelength'] = wavelength d['o1-0_o2-0'] = # port o1 mode0 to port o2 mode0, where mode_index starts in zero for fundamental mode

d['component_settings'] = component.settings d['simulation_settings'] = simulation_settings.

flaport commented 1 year ago

We could do something like this (I will open a PR tomorrow):

image

def old_to_new(df, port_map=None):
    s_headers = sorted({c[:-1] for c in df.columns if c.lower().startswith('s')})
    idxs = sorted({idx for c in s_headers for idx in _s_header_to_port_idxs(c)})
    if port_map is None:
        port_map = {f"o{i}@1": i for i in idxs}
    rev_port_map = {i: p for p, i in port_map.items()}
    assert len(rev_port_map) == len(port_map), "Duplicate port indices found in port_map"
    s_map = {s: tuple(rev_port_map[i] for i in _s_header_to_port_idxs(s)) for s in s_headers}
    dfs = {s: df[['wavelengths', 'freqs', f'{s}m', f'{s}a']].copy().rename(columns={f'{s}m': 'magnitude', f'{s}a': 'phase'}) for s in s_map}
    for s, df_ in dfs.items():
        pm1, pm2 = s_map[s]
        (p1, m1), (p2, m2) = pm1.split('@'), pm2.split('@')
        df_['port_in'], df_['port_out'] = p1, p2
        df_['mode_in'], df_['mode_out'] = m1, m2
    df = pd.concat(dfs.values(), axis=0)[['wavelengths', 'freqs', 'port_in', 'port_out', 'mode_in', 'mode_out', 'magnitude', 'phase']]
    return df

def _s_header_to_port_idxs(s):
    s = re.sub("[^0-9]", "", s)
    inp, out = (int(i) for i in s)
    return inp, out

image

def new_to_old(df, start_idx=1):
    # just making sure...
    df['mode_in'] = np.asarray(df['mode_in'].values, dtype=str)
    df['mode_out'] = np.asarray(df['mode_out'].values, dtype=str)
    df['port_in'] = np.asarray(df['port_in'].values, dtype=str)
    df['port_out'] = np.asarray(df['port_out'].values, dtype=str)

    ports_in = df['port_in'].unique()
    ports_out = df['port_out'].unique()
    modes_in = df['mode_in'].unique()
    modes_out = df['mode_out'].unique()
    ports = sorted(np.unique(np.concatenate([ports_in, ports_out])))
    modes = sorted(np.unique(np.concatenate([modes_in, modes_out])))
    port_modes = {f"{p}@{m}": i for i, (p, m) in enumerate(product(ports, modes), start=start_idx)}
    s_map = {f"s{i}{j}": (p, q) for (p, i), (q, j) in product(port_modes.items(), port_modes.items())}

    new_df = {}
    df_ = None
    for s, (pm1, pm2) in s_map.items():
        p1, m1 = pm1.split('@')
        p2, m2 = pm2.split('@')
        df_ = df.query(f"port_in=={p1!r} & port_out=={p2!r} & mode_in=={m1!r} & mode_out=={m2!r}")
        new_df[f"{s}a"] = df_['phase'].values
        new_df[f"{s}m"] = df_['magnitude'].values

    if df_ is not None:
        new_df[f"wavelengths"] = df_['wavelengths'].values
        new_df[f"freqs"] = df_['freqs'].values

    new_df = pd.DataFrame(new_df)
    return new_df, s_map

image

momchil-flex commented 1 year ago

I don't exactly follow what's happening here, but we've been discussing improvements to Tidy3D's own S-matrix tool. I would suggest eventually using this inside write_sparameters, but I guess even though port names in our tool can be whatever you want, I guess this won't exactly solve the problem as the write_sparameters function itself will still need to change.

One other note: we're implementing our own version of the port symmetries as here, but we realized in the most general case, there can be a minus sign on some of the modes. Specifically, if a port is bisected by a symmetry line and the mode has negative symmetry w.r.t. that line. Then, the relation could be something like S10 = -S20 (e.g. in the case of a y junction where the mode of port 0 that gets split into 1 and 2 is anti-symmetric).

joamatab commented 1 year ago

Nice We are trying to standardize the format where we store s parameters

What format do you plan on using for port symmetries?

What are your thoughts about hdf5, numpy or csv?

It would be great to have a standard both for tidy3d and gdsfactory that can hopefully become the standard for other tools such as circuit simulators

flaport commented 1 year ago

What I'm proposing is two converters between the proposed 'new' format and the old format. The advantage of the new format is that it's a sparse storage system and hence more flexible: you might for example be interested in a denser wavelength sampling for some port combinations (i.e. s-parameters) vs others, moreover it's easier to append port/mode combinations at a later stage which you might not have been initially interested in (just append rows).

HelgeGehring commented 1 year ago
momchil-flex commented 1 year ago

Nice We are trying to standardize the format where we store s parameters

Yeah I think this is somewhat independent of Tidy3D internals, it will be entirely determined by gdsfactory and how it uses the solver.

What format do you plan on using for port symmetries?

Two options we are considering:

What are your thoughts about hdf5, numpy or csv?

We already store SimulationData in hdf5 and have been working on being able to export any Tidy3D model in hdf5 (similarly to how it can be exported to json). This is because we're working on adding more data to Simulation objects, e.g. custom (n, k) dependence, and json is not ideal for that. The nesting of hdf5 makes it a nice choice.

joamatab commented 1 year ago

What do you think of using?

s['o1@0,o1@0'] # port o1, mode=0, to port o1 mode=0 (where mode=0 is fundamental TE for most cases)