diff --git a/README.rst b/README.rst index d4720af8..1208de28 100644 --- a/README.rst +++ b/README.rst @@ -59,7 +59,7 @@ Copyright and License This program is open source under the BSD-3 License. -© 2025. Triad National Security, LLC. All rights reserved. +© 2025. Triad National Security, LLC. All rights reserved. LA-UR-25-29245 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/docs/conf.py b/docs/conf.py index 2ace6604..c0fa4151 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,7 +8,7 @@ exec(open("../dsi/_version.py").read()) project = 'DSI' -copyright = '2025, Triad National Security, LLC. All rights reserved.' +copyright = '2025, Triad National Security, LLC. All rights reserved. LA-UR-25-29248' author = 'The DSI Project team' release = __version__ diff --git a/dsi/backends/duckdb.py b/dsi/backends/duckdb.py index 22d7d8cc..242d0827 100644 --- a/dsi/backends/duckdb.py +++ b/dsi/backends/duckdb.py @@ -68,13 +68,19 @@ def sql_type(self, input_list): `return`: str A string representing the inferred DuckDB data type for the input list. """ - for item in input_list: - if isinstance(item, int): - return " INTEGER" - elif isinstance(item, float): - return " FLOAT" - elif isinstance(item, str): - return " VARCHAR" + DUCKDB_BIGINT_MIN = -9223372036854775808 + DUCKDB_BIGINT_MAX = 9223372036854775807 + DUCKDB_INT_MIN = -2147483648 + DUCKDB_INT_MAX = 2147483647 + + if all(isinstance(x, int) for x in input_list if x is not None): + if any(x < DUCKDB_BIGINT_MIN or x > DUCKDB_BIGINT_MAX for x in input_list if x is not None): + return " DOUBLE" + elif any(x < DUCKDB_INT_MIN or x > DUCKDB_INT_MAX for x in input_list if x is not None): + return " BIGINT" + return " INTEGER" + elif all(isinstance(x, float) for x in input_list if x is not None): + return " DOUBLE" return " VARCHAR" def duckdb_compatible_name(self, name): @@ -822,7 +828,7 @@ def summary_helper(self, table_name): """ col_info = self.cur.execute(f"PRAGMA table_info({table_name})").fetchall() - numeric_types = {'INTEGER', 'REAL', 'FLOAT', 'NUMERIC', 'DECIMAL', 'DOUBLE'} + numeric_types = {'INTEGER', 'REAL', 'FLOAT', 'NUMERIC', 'DECIMAL', 'DOUBLE', 'BIGINT'} headers = ['column', 'type', 'min', 'max', 'avg', 'std_dev'] rows = [] diff --git a/dsi/backends/sqlite.py b/dsi/backends/sqlite.py index ee339113..ef5e89e0 100644 --- a/dsi/backends/sqlite.py +++ b/dsi/backends/sqlite.py @@ -45,12 +45,15 @@ class Sqlite(Filesystem): """ runTable = False - def __init__(self, filename): + def __init__(self, filename, **kwargs): """ Initializes a SQLite backend with a user inputted filename, and creates other internal variables """ self.filename = filename - self.con = sqlite3.connect(filename) + if 'kwargs' in kwargs: + self.con = sqlite3.connect(filename, **kwargs['kwargs']) + else: + self.con = sqlite3.connect(filename) self.cur = self.con.cursor() self.runTable = Sqlite.runTable self.sqlite_keywords = ["ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ALWAYS", "ANALYZE", "AND", "AS", "ASC", "ATTACH", @@ -80,14 +83,16 @@ def sql_type(self, input_list): `return`: str A string representing the inferred SQLite data type for the input list. """ - for item in input_list: - if isinstance(item, int): - return " INTEGER" - elif isinstance(item, float): + SQLITE_INT_MIN = -9223372036854775808 + SQLITE_INT_MAX = 9223372036854775807 + + if all(isinstance(x, int) for x in input_list if x is not None): + if any(x < SQLITE_INT_MIN or x > SQLITE_INT_MAX for x in input_list if x is not None): return " FLOAT" - elif isinstance(item, str): - return " VARCHAR" - return "" + return " INTEGER" + elif all(isinstance(x, float) for x in input_list if x is not None): + return " FLOAT" + return " VARCHAR" def sqlite_compatible_name(self, name): if (name.startswith('"') and name.endswith('"')) or (name.upper() not in self.sqlite_keywords and name.isidentifier()): diff --git a/dsi/core.py b/dsi/core.py index 5b17f7e6..d49e2a70 100644 --- a/dsi/core.py +++ b/dsi/core.py @@ -872,7 +872,7 @@ def wrap_in_quotes(value): self.logger.error(f"Error finding rows due to {return_object[1]}") raise return_object[0](return_object[1]) elif isinstance(return_object, list) and isinstance(return_object[0], str): - err_msg = f"'{column_name}' appeared in more than one table. Can only do a conditional find if '{column_name}' is in one table" + err_msg = f"'{column_name}' appeared in more than one table. Can only find if '{column_name}' is in one table" if self.debug_level != 0: self.logger.warning(err_msg) print(f"WARNING: {err_msg}") @@ -1479,28 +1479,6 @@ def index(self, local_loc, remote_loc, isVerbose=False): with redirect_stdout(fnull): t.load_module('plugin', "Dict", "reader", collection=st_dict, table_name="filesystem") t.artifact_handler(interaction_type='ingest') - - # # Create new filesystem collection with origin and remote locations - # # Stage data for ingest - # # Transpose the OrderedDict to a list of row dictionaries - # num_rows = len(next(iter(st_dict.values()))) # Assume all columns are of equal length - # rows = [] - - # for i in range(num_rows): - # row = {col: values[i] for col, values in st_dict.items()} - # rows.append(row) - - # # Temporary csv to ingest - # output_file = '.fs.csv' - # with open(output_file, mode='w', newline='') as csvfile: - # writer = csv.DictWriter(csvfile, fieldnames=st_dict.keys()) - # writer.writeheader() - # writer.writerows(rows) - - # # Add filesystem table - # t.load_module('plugin', 'Csv', 'reader', filenames=".fs.csv", table_name="filesystem") - # #t.load_module('plugin', 'collection_reader', 'reader', st_dict ) - # t.artifact_handler(interaction_type='ingest') t.close() diff --git a/dsi/dsi.py b/dsi/dsi.py index de668403..9fad77ba 100644 --- a/dsi/dsi.py +++ b/dsi/dsi.py @@ -17,7 +17,7 @@ class DSI(): The DSI Class abstracts Core.Terminal for managing metadata and Core.Sync for data management and movement. ''' - def __init__(self, filename = ".temp.db", backend_name = "Sqlite"): + def __init__(self, filename = ".temp.db", backend_name = "Sqlite", **kwargs): """ Initializes DSI by activating a backend for data operations; default is a Sqlite backend for temporary data analysis. If users specify `filename`, data is saved to a permanent backend file. @@ -61,7 +61,7 @@ def __init__(self, filename = ".temp.db", backend_name = "Sqlite"): try: if backend_name.lower() == 'sqlite': with redirect_stdout(fnull): - self.t.load_module('backend','Sqlite','back-write', filename=filename) + self.t.load_module('backend','Sqlite','back-write', filename=filename, kwargs = kwargs) self.backend_name = "sqlite" elif backend_name.lower() == 'duckdb': with redirect_stdout(fnull): diff --git a/dsi/plugins/file_reader.py b/dsi/plugins/file_reader.py index e26b3751..ebd8577f 100644 --- a/dsi/plugins/file_reader.py +++ b/dsi/plugins/file_reader.py @@ -746,6 +746,8 @@ def add_rows(self) -> None: field_names = [] for element, val in data.items(): if element not in ['authorship', 'data']: + if isinstance(val, list): + val = ",, ".join(val) if element not in temp_data.keys(): temp_data[element] = [val] else: @@ -753,15 +755,17 @@ def add_rows(self) -> None: field_names.append(element) else: for field, val2 in val.items(): + if isinstance(val2, list): + val2 = ",, ".join(val2) if field not in temp_data.keys(): temp_data[field] = [val2] else: temp_data[field].append(val2) field_names.append(field) - if sorted(field_names) != sorted(["name", "description", "data_uses", "creators", "creation_date", - "la_ur", "owner", "funding", "publisher", "published_date", "origin_location", - "num_simulations", "version", "license", "live_dataset"]): + if sorted(field_names) != sorted(["title", "description", "keywords", "instructions_of_use", "authors", + "release_date", "la_ur", "funding", "rights", "file_types", "num_simulations", + "file_size", "num_files", "dataset_size", "version", "doi"]): return (ValueError, f"Error in reading {filename} data card. Please ensure all fields included match the template") self.datacard_data["oceans11_datacard"] = temp_data diff --git a/dsi/plugins/file_writer.py b/dsi/plugins/file_writer.py index 928517eb..38312d02 100644 --- a/dsi/plugins/file_writer.py +++ b/dsi/plugins/file_writer.py @@ -24,7 +24,7 @@ class ER_Diagram(FileWriter): """ DSI Writer that generates an ER Diagram from the current data in the DSI abstraction """ - def __init__(self, filename, target_table_prefix = None, **kwargs): + def __init__(self, filename, target_table_prefix = None, max_cols = None, **kwargs): """ Initializes the ER Diagram writer @@ -35,10 +35,15 @@ def __init__(self, filename, target_table_prefix = None, **kwargs): If provided, filters the ER Diagram to only include tables whose names begin with this prefix. - Ex: If prefix = "student", only "student__address", "student__math", "student__physics" tables are included + + `max_cols` : int, optional, default None + If provided, limits the number of columns displayed for each table in the ER Diagram. + If relational data is included, this must be >= number of primary and foreign keys for a table. """ super().__init__(filename, **kwargs) self.output_filename = filename self.target_table_prefix = target_table_prefix + self.max_cols = max_cols def get_rows(self, collection) -> None: """ @@ -99,7 +104,23 @@ def get_rows(self, collection) -> None: col_list = tableData.keys() if tableName == "dsi_units": - col_list = ["table_name", "column_and_unit"] + col_list = ["table_name", "column_name", "unit"] + if self.max_cols is not None: + if "dsi_relations" in collection.keys(): + fk_cols = [t[1] for t in collection["dsi_relations"]["foreign_key"] if t[0] == tableName] + pk_cols = [t[1] for t in collection["dsi_relations"]["primary_key"] if t[0] == tableName] + rel_cols = set(pk_cols + fk_cols) + + if rel_cols: + if len(rel_cols) > self.max_cols: + return (ValueError, "'max_cols' must be >= to the number of primary/foreign key columns.") + other_cols = [col for col in col_list if col not in rel_cols] + combined = list(rel_cols) + other_cols[:self.max_cols - len(rel_cols)] + col_list = [k for k in col_list if k in combined] + col_list = col_list[:self.max_cols] + if len(tableData.keys()) > self.max_cols: + col_list.append("...") + curr_row = 0 inner_brace = 0 for col_name in col_list: @@ -121,9 +142,9 @@ def get_rows(self, collection) -> None: if "dsi_relations" in collection.keys(): for f_table, f_col in collection["dsi_relations"]["foreign_key"]: - if self.target_table_prefix is not None and self.target_table_prefix not in f_table: + if self.target_table_prefix is not None and f_table is not None and self.target_table_prefix not in f_table: continue - if f_table != None: + if f_table is not None: foreignIndex = collection["dsi_relations"]["foreign_key"].index((f_table, f_col)) fk_string = f"{f_table}:{f_col}" pk_string = f"{collection['dsi_relations']['primary_key'][foreignIndex][0]}:{collection['dsi_relations']['primary_key'][foreignIndex][1]}" @@ -137,7 +158,10 @@ def get_rows(self, collection) -> None: subprocess.run(["dot", "-T", file_type[1:], "-o", self.output_filename + file_type, self.output_filename + ".dot"]) os.remove(self.output_filename + ".dot") else: - dot.render(self.output_filename, cleanup=True) + try: + dot.render(self.output_filename, cleanup=True) + except: + return (EnvironmentError, "Graphviz executable must be downloaded to global environment using sudo or homebrew.") class Csv_Writer(FileWriter): """ diff --git a/dsi/tests/test_dsi.py b/dsi/tests/test_dsi.py index 700f541b..0ed78305 100644 --- a/dsi/tests/test_dsi.py +++ b/dsi/tests/test_dsi.py @@ -599,7 +599,7 @@ def test_find_partial_sqlite_backend(): assert find_df is None expected_output = textwrap.dedent("""\ Finding all rows where 'specification~~y1' in the active backend\n - WARNING: 'specification' appeared in more than one table. Can only do a conditional find if 'specification' is in one table + WARNING: 'specification' appeared in more than one table. Can only find if 'specification' is in one table Try using `dsi.query()` to retrieve the matching rows for a specific table These are recommended inputs for query(): - SELECT * FROM math WHERE CAST(specification AS TEXT) LIKE '%y1%' @@ -823,7 +823,7 @@ def test_find_relation_error_sqlite_backend(): assert find_df is None expected_output = textwrap.dedent("""\ Finding all rows where 'specification = '!jack'' in the active backend\n - WARNING: 'specification' appeared in more than one table. Can only do a conditional find if 'specification' is in one table + WARNING: 'specification' appeared in more than one table. Can only find if 'specification' is in one table Try using `dsi.query()` to retrieve the matching rows for a specific table These are recommended inputs for query(): - SELECT * FROM math WHERE specification = '!jack' @@ -945,10 +945,10 @@ def test_query_duckdb_backend(): output = "\n".join(output.splitlines()[1:]) excepted_output = textwrap.dedent(""" - specification | n | o | p | q | r | s - ----------------------------------------------------------------------------------------- - !amy | 9.800000190734863 | gravity | 23 | home 23 | 1 | -0.0012000000569969416 - !amy1 | 91.80000305175781 | gravity | 233 | home 23 | 12 | -0.012199999764561653 + specification | n | o | p | q | r | s + ------------------------------------------------------------- + !amy | 9.8 | gravity | 23 | home 23 | 1 | -0.0012 + !amy1 | 91.8 | gravity | 233 | home 23 | 12 | -0.0122 """) assert output == excepted_output @@ -956,7 +956,7 @@ def test_query_duckdb_backend(): assert isinstance(query_data, DataFrame) assert query_data.columns.tolist() == ['dsi_table_name','specification','n','o','p','q','r','s'] assert len(query_data) == 2 - assert query_data["n"].tolist() == [9.800000190734863, 91.80000305175781] + assert query_data["n"].tolist() == [9.8, 91.8] assert query_data["dsi_table_name"][0] == "physics" def test_query_update_duckdb_backend(): @@ -1018,27 +1018,27 @@ def test_search_duckdb_backend(): Table: address - Columns: ['specification', 'fileLoc', 'g', 'h', 'i', 'j', 'k', 'l', 'm'] - Row Number: 1 - - Data: ['!sam', '/home/sam/lib/data', 'good memories', 9.800000190734863, 2, 3, 4, 1.0, 99] + - Data: ['!sam', '/home/sam/lib/data', 'good memories', 9.8, 2, 3, 4, 1.0, 99] Table: math - Columns: ['specification', 'a', 'b', 'c', 'd', 'e', 'f'] - Row Number: 1 - - Data: ['!jack', 1, 2, 45.97999954223633, 2, 34.79999923706055, 0.008899999782443047] + - Data: ['!jack', 1, 2, 45.98, 2, 34.8, 0.0089] Table: math - Columns: ['specification', 'a', 'b', 'c', 'd', 'e', 'f'] - Row Number: 2 - - Data: ['!jack1', 2, 3, 45.97999954223633, 3, 44.79999923706055, 0.00989999994635582] + - Data: ['!jack1', 2, 3, 45.98, 3, 44.8, 0.0099] Table: physics - Columns: ['specification', 'n', 'o', 'p', 'q', 'r', 's'] - Row Number: 1 - - Data: ['!amy', 9.800000190734863, 'gravity', 23, 'home 23', 1, -0.0012000000569969416] + - Data: ['!amy', 9.8, 'gravity', 23, 'home 23', 1, -0.0012] Table: physics - Columns: ['specification', 'n', 'o', 'p', 'q', 'r', 's'] - Row Number: 2 - - Data: ['!amy1', 91.80000305175781, 'gravity', 233, 'home 23', 12, -0.012199999764561653] + - Data: ['!amy1', 91.8, 'gravity', 233, 'home 23', 12, -0.0122] """) assert output == expected_output @@ -1073,9 +1073,9 @@ def test_sanitize_inputs_duckdb(): Table: "all" - specification | fileLoc | G | all | i | j | k | l | m - --------------------------------------------------------------------------------------------- - !sam | /home/sam/lib/data | good memories | 9.800000190734863 | 2 | 3 | 4 | 1.0 | 99 + specification | fileLoc | G | all | i | j | k | l | m + ------------------------------------------------------------------------------- + !sam | /home/sam/lib/data | good memories | 9.8 | 2 | 3 | 4 | 1.0 | 99 """) assert output == expected_output @@ -1114,10 +1114,10 @@ def test_sanitize_inputs_duckdb(): expected_output = '\nTable: math' + textwrap.dedent(""" - specification | a | math | c | d | e | f - ---------------------------------------------------------------------------------------------- - None | nan | nan | nan | nan | nan | nan - !jack1 | 2.0 | 4.0 | 45.97999954223633 | 3.0 | 44.79999923706055 | 0.00989999994635582 + specification | a | math | c | d | e | f + -------------------------------------------------------- + None | nan | nan | nan | nan | nan | nan + !jack1 | 2.0 | 4.0 | 45.98 | 3.0 | 44.8 | 0.0099 """) assert output == expected_output @@ -1140,9 +1140,9 @@ def test_sanitize_inputs_duckdb(): expected_output = '\nTable: "2"' + textwrap.dedent(""" - specification | a | b | c | d | e | f - ----------------------------------------------------------------------------------------- - !jack | 1 | 99 | 45.97999954223633 | 2 | 34.79999923706055 | 0.008899999782443047 + specification | a | b | c | d | e | f + -------------------------------------------------- + !jack | 1 | 99 | 45.98 | 2 | 34.8 | 0.0089 """) assert output == expected_output @@ -1466,7 +1466,7 @@ def test_find_partial_duckdb_backend(): assert find_df is None expected_output = textwrap.dedent("""\ Finding all rows where 'specification~~y1' in the active backend\n - WARNING: 'specification' appeared in more than one table. Can only do a conditional find if 'specification' is in one table + WARNING: 'specification' appeared in more than one table. Can only find if 'specification' is in one table Try using `dsi.query()` to retrieve the matching rows for a specific table These are recommended inputs for query(): - SELECT * FROM address WHERE CAST(specification AS TEXT) ILIKE '%y1%' @@ -1690,7 +1690,7 @@ def test_find_relation_error_duckdb_backend(): assert find_df is None expected_output = textwrap.dedent("""\ Finding all rows where 'specification = '!jack'' in the active backend\n - WARNING: 'specification' appeared in more than one table. Can only do a conditional find if 'specification' is in one table + WARNING: 'specification' appeared in more than one table. Can only find if 'specification' is in one table Try using `dsi.query()` to retrieve the matching rows for a specific table These are recommended inputs for query(): - SELECT * FROM address WHERE specification = '!jack' diff --git a/examples/pennant/pennant_oceans11.yml b/examples/pennant/pennant_oceans11.yml index 0e5cda50..0801c0ed 100644 --- a/examples/pennant/pennant_oceans11.yml +++ b/examples/pennant/pennant_oceans11.yml @@ -1,19 +1,20 @@ -name: "PENNANT" +title: "PENNANT" description: "PENNANT is an unstructured mesh physics mini-app designed for advanced architecture research. It contains mesh data structures and a few physics algorithms adapted from the LANL rad-hydro code FLAG, and gives a sample of the typical memory access patterns of FLAG." -data_uses: "" +keywords: ["key1", "key2", "key3"] +instructions_of_use: "Follow instructions in the README in dsi/examples/wildfire/" authorship: - creators: "Charles R. Ferenbaugh" - creation_date: "March 2012" + authors: ["Charles R. Ferenbaugh"] + release_date: "March 2012" la_ur: "LA-CC-12-021" - owner: "Charles R. Ferenbaugh" funding: "" - publisher: "" - published_date: "February 2016" + rights: "Copyright (c) 2012, Triad National Security, LLC. All rights reserved. This software was produced under U.S. Government contract 89233218CNA000001 for Los Alamos National Laboratory (LANL), which is operated by Triad National Security, LLC for the U.S. Department of Energy/National Nuclear Security Administration." data: - origin_location: "oceans11 open data server" - num_simulations: 0 + file_types: "OUT file" + num_simulations: 1 + file_size: "885 bytes" + num_files: 10 + dataset_size: "8.85 KB" version: 0.9 - license: "Copyright (c) 2012, Triad National Security, LLC. All rights reserved. This software was produced under U.S. Government contract 89233218CNA000001 for Los Alamos National Laboratory (LANL), which is operated by Triad National Security, LLC for the U.S. Department of Energy/National Nuclear Security Administration." - live_dataset: No \ No newline at end of file + doi: "doi url" \ No newline at end of file diff --git a/examples/test/coreterminal.py b/examples/test/coreterminal.py index e765411b..8793430a 100644 --- a/examples/test/coreterminal.py +++ b/examples/test/coreterminal.py @@ -7,16 +7,16 @@ # a.load_module('plugin','Bueno','reader', filenames=['bueno1.data', 'bueno2.data']) # a.load_module('plugin','Hostname','reader') -# a.load_module('plugin', 'Schema', 'reader', filename="example_schema.json") +a.load_module('plugin', 'Schema', 'reader', filename="example_schema.json") # a.load_module('plugin', 'Schema', 'reader', filename="yaml1_schema.json") -# a.load_module('plugin', 'YAML1', 'reader', filenames=["student_test1.yml", "student_test2.yml"]) +a.load_module('plugin', 'YAML1', 'reader', filenames=["student_test1.yml", "student_test2.yml"]) # a.load_module('plugin', 'TOML1', 'reader', filenames=["results.toml", "results1.toml"], target_table_prefix = "results") # a.load_module('plugin', 'Csv', 'reader', filenames="yosemite5.csv") # a.load_module('plugin', 'Ensemble', 'reader', filenames="wildfiredata.csv") # a.load_module('plugin', 'Cloverleaf', 'reader', folder_path="../clover3d/") -a.load_module('plugin', 'Oceans11Datacard', 'reader', filenames=['../wildfire/wildfire_oceans11.yml', '../pennant/pennant_oceans11.yml']) +# a.load_module('plugin', 'Oceans11Datacard', 'reader', filenames=['../wildfire/wildfire_oceans11.yml', '../pennant/pennant_oceans11.yml']) # a.load_module('plugin', 'DublinCoreDatacard', 'reader', filenames="../wildfire/wildfire_dublin_core.xml") # a.load_module('plugin', 'SchemaOrgDatacard', 'reader', filenames="../wildfire/wildfire_schema_org.json") # a.load_module('plugin', 'GoogleDatacard', 'reader', filenames="../wildfire/wildfire_google.yml") @@ -28,7 +28,7 @@ # a.transload() -''' Example of loading a Sqlite DSI backend, and its data interactions: ingest, query, notebook, process ''' +''' Example of loading a DSI backend, and its data interactions: ingest, query, notebook, process ''' a.load_module('backend','Sqlite','back-write', filename='data.db') # a.load_module('backend','DuckDB','back-write', filename='data.db') diff --git a/examples/test/template_dc_oceans11.yml b/examples/test/template_dc_oceans11.yml index 1cb88575..9ff4c88a 100644 --- a/examples/test/template_dc_oceans11.yml +++ b/examples/test/template_dc_oceans11.yml @@ -1,19 +1,20 @@ -name: "data set name" +title: "data set name" description: "" -data_uses: "" +keywords: [] +instructions_of_use: "software/platform necessary to open the data and how to run it" authorship: - creators: "" - creation_date: "" + authors: [] + release_date: "" la_ur: "" - owner: "" funding: "" - publisher: "" - published_date: "" + rights: "" data: - origin_location: "" + file_types: "" num_simulations: 0 + file_size: "" + num_files: 0 + dataset_size: "" version: 0 - license: "" - live_dataset: No \ No newline at end of file + doi: "" \ No newline at end of file diff --git a/examples/wildfire/wildfire_oceans11.yml b/examples/wildfire/wildfire_oceans11.yml index 9ce70f4e..d0586183 100644 --- a/examples/wildfire/wildfire_oceans11.yml +++ b/examples/wildfire/wildfire_oceans11.yml @@ -1,19 +1,20 @@ -name: "Vorticity-Driven Lateral Spread Ensemble Data Set" +title: "Firetec" description: "This ensemble data set presents the study of wildfires, and more specifically, the phenomena known as vorticity-driven lateral spread (VLS) [1], in wildfires of mountain and canyon topographies." -data_uses: "" +keywords: ["key2", "key3"] +instructions_of_use: "run wildfire.py in dsi/examples/wildfire/" authorship: - creators: "Divya Banesh, Rodman Ray Linn, John M Patchett" - creation_date: "October 28, 2021" + authors: ["Divya Banesh", "Rodman Ray Linn", "John M Patchett"] + release_date: "October 28, 2021" la_ur: "LA-UR-21-30892" - owner: "Divya Banesh, Rodman Ray Linn, John M Patchett" funding: "" - publisher: "IEEE Vis, 2022-10-23 (Oklahoma City, Oklahoma, United States)" - published_date: "2023-02-23" + rights: "Copyright (c) 2021, Triad National Security, LLC. All rights reserved." data: - origin_location: "oceans11 open data server" + file_types: "CSV" num_simulations: 6 + file_size: "1 GB" + num_files: 75 + dataset_size: "450 GB" version: 1.0 - license: "Copyright (c) 2021, Triad National Security, LLC. All rights reserved." - live_dataset: No \ No newline at end of file + doi: "doi url" \ No newline at end of file