From 37d16710ffcaa05adce9de25cfe05042c4a62ec3 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Mon, 28 Oct 2024 20:25:04 +0100 Subject: [PATCH] More documentation cleanup --- doc/conf.py.in | 14 +- doc/getting_started/build/build.md | 2 +- doc/getting_started/tutorial.md | 299 +++++++++++++++++------------ doc/user_guide/datamodels.md | 14 +- doc/user_guide/storage_plugins.md | 18 +- 5 files changed, 211 insertions(+), 136 deletions(-) diff --git a/doc/conf.py.in b/doc/conf.py.in index 8f7ef6bc4..75559a026 100644 --- a/doc/conf.py.in +++ b/doc/conf.py.in @@ -59,7 +59,7 @@ extensions = [ "sphinx.ext.napoleon", # API ref Google and NumPy style "sphinx.ext.viewcode", "sphinxcontrib.plantuml", # PlantUml - #"sphinx_toggleprompt", # Add button for show/hide example prompts + "sphinx_toggleprompt", # Add button for show/hide example prompts "sphinx_copybutton", # Copy button for codeblocks "sphinx_design", # Create panels in a grid layout or as drop-downs and more ] @@ -186,3 +186,15 @@ def autoapi_prepare_jinja_env(jinja_env: "Environment") -> None: jinja_env.filters["remove_plugin_path"] = lambda name: ".".join( name.split(".")[1:] ) if name.split(".")[0] in dlite_share_plugins else name + + + +def skip_dlite_dlite_members(app, what, name, obj, skip, options): + """Skip class and function whos names contains 'dlite.dlite.'.""" + if "dlite.dlite." in name: + return True + return None + + +def setup(sphinx): + sphinx.connect("autoapi-skip-member", skip_dlite_dlite_members) diff --git a/doc/getting_started/build/build.md b/doc/getting_started/build/build.md index 04c90753b..9a42d2726 100644 --- a/doc/getting_started/build/build.md +++ b/doc/getting_started/build/build.md @@ -92,7 +92,7 @@ Using [Visual Studio Code] (VS Code) it is possible to do development on the sys 1. Download and install [Visual Studio Code]. 2. Install the extension **Remote Development**. -3. [Clone DLite](#installing-from-source) +3. [Clone DLite](#build-from-source) 4. Open the DLite folder with VS Code. 5. Start VS Code, run the _Remote-Containers: Open Folder in Container..._ command from the Command Palette (F1) or quick actions Status bar item. This will build the container and restart VS Code in it. diff --git a/doc/getting_started/tutorial.md b/doc/getting_started/tutorial.md index 4495c475a..fe53bde32 100644 --- a/doc/getting_started/tutorial.md +++ b/doc/getting_started/tutorial.md @@ -1,7 +1,11 @@ -# Tutorial +Tutorial +======== -## Introduction -This tutorial shows the steps for creating and instantiating a Dlite entity, and populate it with data. For this, we will use an example `csv` file containing data corresponding to time measurements from a solar panel. The example file has the following format: +Introduction +------------ +This tutorial shows the steps for creating and instantiating a Dlite entity, and populate it with data. +For this, we will use an example `csv` file containing data corresponding to time measurements from a solar panel. +The example file has the following format: | time | Impp [A] | Isc [A] | MPP [W] | Vmpp [V] | Voc [V] | @@ -11,14 +15,18 @@ This tutorial shows the steps for creating and instantiating a Dlite entity, and -## Writing a DLite Entity -Let us write an Entity representing our solar panel measurement. We start by creating a `json` file called `myEntity.json`. +Writing a DLite Entity +---------------------- +Let us write an Entity representing our solar panel measurement. +We start by creating a `json` file called `myEntity.json`. ### **Step 1**: Giving it a unique identifier -Firstly, we must provide a unique identifier for our Entity. There are several ways of doing this: +Firstly, we must provide a unique identifier for our Entity. +There are several ways of doing this: #### **1. Uniform Resource Identifier (URI)** -We can create and assign a unique Uniform Resource Identifier (URI). A URI has the form **namespace/version/nameOfEntity**, and it can be either written directly in the json file: +We can create and assign a unique Uniform Resource Identifier (URI). +A URI has the form **namespace/version/nameOfEntity**, and it can be either written directly in the json file: ```json "uri": "http://www.ontotrans.eu/0.1/solarPanelMeasurement", @@ -32,27 +40,38 @@ or we can give the **namespace**, **version**, and **name** of our Entity as sep ``` #### **2. Universally Unique Identifier (UUID)** -Alternatively, we can generate and assign a universally unique identifier (UUID). In Python, this can be achieved by the use of the `dlite.get_uuid()` function or the [dlite-getuuid](https://github.com/SINTEF/dlite/blob/master/doc/user_guide/tools.md#dlite-getuuid) tool. +Alternatively, we can generate and assign a universally unique identifier (UUID). +In Python, this can be achieved by the use of the `dlite.get_uuid()` function or the [dlite-getuuid] tool. ```python -import uuid + >>> import uuid -# Generate a random UUID -entity_uuid = uuid.uuid4() + # Generate a random UUID + >>> entity_uuid = uuid.uuid4() + + >>> with open("myEntity.json", "w") as entity_file: + ... entity_file.write('"uuid": {}'.format(entity_uuid)) -with open("myEntity.json", "w") as entity_file: - entity_file.write('"uuid": {}'.format(entity_uuid)) ``` Our json file now contains the following: + ```json -"uuid": +"uuid": "" ``` + where `` is a unique identifier for our Entity. ### **Step 2**: Providing a link to the metadata -Let us assume that we have followed option 1 and provided a URI. The next step consists in adding a **meta** field that will link the Entity to its metadata. In DLite, all Entities are instances of the *EntitySchema*. The *EntitySchema* defines the structure of the Entity. In the **meta** field of our Entity, we need to provide the URI of the EntitySchema. For a schematic overview of the DLite data structures and further detail, see [Concepts section of the DLite User Guide](https://sintef.github.io/dlite/user_guide/concepts.html). We add this information to our json file in the following manner: +Let us assume that we have followed option 1 and provided a URI. +The next step consists in adding a **meta** field that will link the Entity to its metadata. +In DLite, all Entities are instances of the *EntitySchema*. +The *EntitySchema* defines the structure of the Entity. +In the **meta** field of our Entity, we need to provide the URI of the EntitySchema. +For a schematic overview of the DLite data structures and further detail, see [Concepts section of the DLite User Guide]. +We add this information to our json file in the following manner: + ```json "uri": "http://www.ontotrans.eu/0.1/solarPanelMeasurement", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", @@ -63,7 +82,8 @@ The **meta** field actually defaults to `http://onto-ns.com/meta/0.3/EntitySchem ::: ### **Step 3**: Adding a human-understandable description -Next, we want to include a human-understandable description of what the Entity represents. In our example case, such a **description** field could be +Next, we want to include a human-understandable description of what the Entity represents. +In our example case, such a **description** field could be ```json "uri": "http://www.ontotrans.eu/0.1/solarPanelMeasurement", @@ -72,7 +92,10 @@ Next, we want to include a human-understandable description of what the Entity r ``` ### **Step 4**: Defining the dimensions -We continue by defining the dimensions for our Entity. Each dimension should have a **name** and a human-understandable **description**. In our example case, we only have one dimension since all the quantities we are interested in are measured with the same frequency (i.e., we have the same number of data points for all the measured quantities). Note that even though we only have one dimension, we need to give it in a list format. +We continue by defining the dimensions for our Entity. +Each dimension should have a **name** and a human-understandable **description**. +In our example case, we only have one dimension since all the quantities we are interested in are measured with the same frequency (i.e., we have the same number of data points for all the measured quantities). +Note that even though we only have one dimension, we need to give it in a list format. We will give our dimension the generic name "N", and describe it as the number of measurements: @@ -145,9 +168,13 @@ Inserting the properties displayed in the table above, our Entity is complete an Both dimensions and properties can also be provided as arrays using a `dict` with the name as key. This may look like ```json +{ "dimensions": [ - {"name": "N", "description": "Number of measurements."} - ] + { + "name": "N", + "description": "Number of measurements." + } + ], "properties": [ { "name": "t", @@ -163,90 +190,96 @@ Both dimensions and properties can also be provided as arrays using a `dict` wit "shape": ["N"], "description": "Maximum Power" }, - ... + ... ] +} ``` + DLite supports both syntaxes. ::: -## Loading an Entity with DLite +Loading an Entity with DLite +---------------------------- We will now load our Entity in Python. There are several ways of doing this. ### 1. Using the json storage plugin: + ```python -import dlite + >>> import dlite -SolarPanelEntity = dlite.Instance.from_location('json', 'myEntity.json') + >>> SolarPanelEntity = dlite.Instance.from_location('json', 'myEntity.json') ``` ### 2. Adding the file to `storage_path` -In this method, we append the `json` file to `storage_path` and then use the [get_instance()](https://sintef.github.io/dlite/autoapi/dlite/index.html?highlight=get_instance#dlite.get_instance) function to load the Entity using its URI. In Python, this may be done by +In this method, we append the `json` file to `storage_path` and then use the [get_instance()] function to load the Entity using its URI. +In Python, this may be done by ``` python -import dlite + >>> import dlite + + # Append the entity file path to the search path for storages + >>> dlite.storage_path.append('myEntity.json') -# Append the entity file path to the search path for storages -dlite.storage_path.append('myEntity.json') + # Load Entity using its uri + >>> SolarPanelEntity = dlite.get_instance("http://www.ontotrans.eu/0.1/solarPanelMeasurement") -# Load Entity using its uri -SolarPanelEntity = dlite.get_instance("http://www.ontotrans.eu/0.1/solarPanelMeasurement") ``` **Note:** The same approach applies if a UUID is used. -## Connect loaded entity to data - -Now that we have loaded our Entity, we can instantiate it, and populate with the data from the `csv` file mentioned in the introduction. Note that you may have to provide a full file path to the `read_csv()` function, depending on where you have stored your `csv` file. +Connect loaded entity to data +----------------------------- +Now that we have loaded our Entity, we can instantiate it, and populate with the data from the `csv` file mentioned in the introduction. +Note that you may have to provide a full file path to the `read_csv()` function, depending on where you have stored your `csv` file. ```python -import pandas as pd + >>> import pandas as pd -# Read csv file using pandas, store its contents in a pandas DataFrame -solar_panel_dataframe = pd.read_csv('data.csv', sep=',', header = 0) + # Read csv file using pandas, store its contents in a pandas DataFrame + >>> solar_panel_dataframe = pd.read_csv('data.csv', sep=',', header = 0) -# Instantiate an instance of the Entity that we loaded above -inst = SolarPanelEntity({"N": len(solar_panel_dataframe)}) + # Instantiate an instance of the Entity that we loaded above + >>> inst = SolarPanelEntity({"N": len(solar_panel_dataframe)}) -# Create a dicitionary that defines the mapping between property names and column headers -mapping = { - "t":"time", - "MPP":"MPP [W]", - "voc":"Voc [V]", - "vmpp":"Vmpp [V]", - "isc" :"Isc [A]", - "impp":"Impp [A]" + # Create a dicitionary that defines the mapping between property names and column headers + >>> mapping = { + ... "t": "time", + ... "MPP": "MPP [W]", + ... "voc": "Voc [V]", + ... "vmpp": "Vmpp [V]", + ... "isc" : "Isc [A]", + ... "impp": "Impp [A]", + ... } -} - -# Loop through the dictionary keys and populate the instance with data -for key in mapping: - inst[key] = solar_panel_dataframe[mapping[key]] + # Loop through the dictionary keys and populate the instance with data + >>> for key in mapping: + ... inst[key] = solar_panel_dataframe[mapping[key]] ``` -More examples of how to use DLite are available in the [examples](https://github.com/SINTEF/dlite/tree/master/examples) directory. - +More examples of how to use DLite are available in the [examples] directory. -## Working with physical quantities +Working with physical quantities +-------------------------------- As you can read in the previous sections, a property of a dlite entity can be -defined with a **unit** (a unit of measurement). The dlite.Instance object can -set (or get) their properties from (or to) physical quanities (the product of a -numerical value and a unit of measurement). The dlite library uses the physical -quantities defined and implemented by the Python package -[pint](https://pint.readthedocs.io/en/stable/getting/overview.html). +defined with a **unit** (a unit of measurement). +The dlite.Instance object can set (or get) their properties from (or to) physical quanities (the product of a numerical value and a unit of measurement). +The dlite library uses the physical quantities defined and implemented by the Python package [pint]. -> You must install pint if you want to to work with physical quantities, -> try to run the command ```pip install pint```, see the page -> [pint installation](https://pint.readthedocs.io/en/stable/getting/index.html#installation). +```{note} +You must install pint if you want to to work with physical quantities, +try to run the command `pip install pint`, see the page [pint installation]. +``` This section illustrates how to work with physical quantities when manipulating -the dlite **instance** objects. The code example will use the following data model: +the dlite **instance** objects. +The code example will use the following data model: ```yaml uri: http://onto-ns.com/meta/0.1/BodyMassIndex @@ -276,73 +309,103 @@ properties: description: Underweight, normal, or overweight. ``` -The code below will create one BodyMassIndex instance object (variable ```person```) and assign the properties of the ```person``` using physical quantities. You can use the method ```person.set_quantity``` to set a property with a quantity or you can use the shortcut ```person.q``` like shown below: +The code below will create one BodyMassIndex instance object (variable `person`) and assign the properties of the `person` using physical quantities. +You can use the method `person.set_quantity` to set a property with a quantity or you can use the shortcut `person.q` like shown below: ```python -BodyMassIndex = dlite.get_instance("http://onto-ns.com/meta/0.1/BodyMassIndex") -# create a BodyMassIndex instance -person = BodyMassIndex() -# assign the name of the person -person.name = "John Doe" -# assign the age of the person without a physical quantity -person.age = 5 -assert person.age == 5.0 -# assign the age of the person with a physical quantity -person.set_quantity("age", 3.0, "years") -assert person.age == 3.0 -person.q.age = (1461, "days") -assert person.age == 4.0 -# assign the height and the weight -person.q.height = "1.72m" -person.q.weight = "63kg" -assert person.height == 172.0 -assert person.weight == 63.0 -# compute the BMI -person.q.bmi = person.q.weight.to("kg") / person.q.height.to("m") ** 2 -assert np.round(person.get_quantity("bmi").magnitude) == 21.0 -# the following line will give the same result as the line above -person.q.bmi = person.q.weight / person.q.height ** 2 -# the following line will raise a TypeError exception, because the property -# "category" with the type "string" cannot be converted into a quantity. -category = person.q.category -# assign the category -person.category = "normal" -if person.bmi < 18.5: - person.category = = "underweight" -elif person.bmi >= 25.0: - person.category = "overweight" + >>> BodyMassIndex = dlite.get_instance("http://onto-ns.com/meta/0.1/BodyMassIndex") + + # create a BodyMassIndex instance + >>> person = BodyMassIndex() + + # assign the name of the person + >>> person.name = "John Doe" + + # assign the age of the person without a physical quantity + >>> person.age = 5 + >>> assert person.age == 5.0 + + # assign the age of the person with a physical quantity + >>> person.set_quantity("age", 3.0, "years") + >>> assert person.age == 3.0 + >>> person.q.age = (1461, "days") + >>> assert person.age == 4.0 + + # assign the height and the weight + >>> person.q.height = "1.72m" + >>> person.q.weight = "63kg" + >>> assert person.height == 172.0 + >>> assert person.weight == 63.0 + + # compute the BMI + >>> person.q.bmi = person.q.weight.to("kg") / person.q.height.to("m") ** 2 + >>> assert np.round(person.get_quantity("bmi").magnitude) == 21.0 + + # the following line will give the same result as the line above + >>> person.q.bmi = person.q.weight / person.q.height ** 2 + + # the following line will raise a TypeError exception, because the property + # "category" with the type "string" cannot be converted into a quantity. + >>> category = person.q.category + + # assign the category + >>> person.category = "normal" + >>> if person.bmi < 18.5: + ... person.category = = "underweight" + ... elif person.bmi >= 25.0: + ... person.category = "overweight" + ``` -> If you need in your program to use a specific units registry, use the function -> [pint.set_application_registry](https://pint.readthedocs.io/en/0.10.1/tutorial.html#using-pint-in-your-projects) +```note +If you need in your program to use a specific units registry, use the function +[`pint.set_application_registry()`] +``` -The Python property ```q``` of the dlite.Instance objects as some other methods: +The Python property `q` of the dlite.Instance objects as some other methods: ```python -assert person.q.names() == ["age", "height", "weight", "bmi"] -assert person.q.units() == ["year", "cm", "kg", "kg/m^2"] -for name, quantity in persion.q.items(): - quantity.defaut_format = "~" # format the unit with a compact format - print(f"{name} = {quantity}") + >>> assert person.q.names() == ["age", "height", "weight", "bmi"] + >>> assert person.q.units() == ["year", "cm", "kg", "kg/m^2"] + >>> for name, quantity in persion.q.items(): + ... quantity.defaut_format = "~" # format the unit with a compact format + ... print(f"{name} = {quantity}") + ``` -> see [quantity formatting](https://pint.readthedocs.io/en/stable/user/formatting.html) +See [quantity formatting]. -You should not keep the Python property ```q``` as a local variable, it will result with wrong assigment if you work with several instances. +You should not keep the Python property `q` as a local variable, it will result with wrong assignment if you work with several instances. ```python -# >>> don't do that -p1 = person1.q -p2 = person2.q -# this will assign the weight to person2, and not to person1 -p1.weight = "34kg" -# <<< -# you must write this code -person1.q.weight = "34kg" -person2.q.weight = "32kg" -# for more complex formula, you can get the quantities in local variables -w1, h1 = person1.q.get("weight", "height") -w2, h2, a2 = person2.q.get("weight", "height", "age") + # Don't do this! + >>> p1 = person1.q + >>> p2 = person2.q + # this will assign the weight to person2, and not to person1 + >>> p1.weight = "34kg" + + # Instead, you should write the code like this + >>> person1.q.weight = "34kg" + >>> person2.q.weight = "32kg" + + # For more complex formulas, you can assign the quantities to local variables + >>> w1, h1 = person1.q.get("weight", "height") + >>> w2, h2, a2 = person2.q.get("weight", "height", "age") + ``` -The first line of the code above prepare the [singleton](https://www.geeksforgeeks.org/singleton-pattern-in-python-a-complete-guide/) dlite.quantity.QuantityHelper for the person1 and return the singleton. The second line do the same as the first line, so the variable ```p2``` is the singleton and is prepared to work on ```person2```. \ No newline at end of file +The first line of the code above prepare the [singleton] `dlite.quantity.QuantityHelper` for the person1 and return the singleton. +The second line do the same as the first line, so the variable `p2` is the singleton and is prepared to work on `person2`. + + + +[dlite-getuuid]: https://github.com/SINTEF/dlite/blob/master/doc/user_guide/tools.md#dlite-getuuid +[Concepts section of the DLite User Guide]: https://sintef.github.io/dlite/user_guide/concepts.html +[get_instance()]: https://sintef.github.io/dlite/autoapi/dlite/index.html?highlight=get_instance#dlite.get_instance + +[examples]: https://github.com/SINTEF/dlite/tree/master/examples +[pint]: https://pint.readthedocs.io/en/stable/getting/overview.html +[singleton]: https://www.geeksforgeeks.org/singleton-pattern-in-python-a-complete-guide/ +[quantity formatting]: https://pint.readthedocs.io/en/stable/user/formatting.html +[`pint.set_application_registry()`]: https://pint.readthedocs.io/en/0.10.1/tutorial.html#using-pint-in-your-projects +[pint installation]: https://pint.readthedocs.io/en/stable/getting/index.html#installation diff --git a/doc/user_guide/datamodels.md b/doc/user_guide/datamodels.md index 76c2c455e..84899adef 100644 --- a/doc/user_guide/datamodels.md +++ b/doc/user_guide/datamodels.md @@ -1,14 +1,14 @@ Representing a datamodel (entity) ----------------------------------- +================================= The underlying structure of DLite datamodels are described under [concepts]. Here, at set of rules on how to create a datamodel is presented. Note that several other possibilities are avilable, and this can be seen in the -examples and tests present in the repository. +examples and tests present in the repository. -We choose here to present only one method as mixing reprentation methods might +We choose here to present only one method as mixing reprentation methods might be confusing. Note, however that yaml and json representations are interchangable. A generic example with some comments for clarity can be seen below. @@ -31,8 +31,8 @@ The keywords in the datamodel have the following meaning: * `uri`: A URI that uniquely identifies the datamodel. * `description`: A human description that describes what this datamodel represents. * `dimensions`: Dimensions of the properties (referred to by the property shape). Properties can have the same dimensions, but not necessarily. Each dimension is described by: - - name of the dimension - - a human description of the dimension + - name of the dimension + - a human description of the dimension In the below example there is one dimension with name "N" and description "Number of skills." * `properties`: Sub-parts of the datamodel that describe the individual data fields. A property has a name and is further specified by the following keywords: - `description`: Human description of the property. @@ -48,7 +48,7 @@ A slightly more realistic example is the "Person" entity, where we want to descr ```yaml uri: http://onto-ns.com/meta/0.1/Person description: A person. -dimensions: +dimensions: N: Number of skills. properties: name: @@ -66,7 +66,7 @@ properties: dlite-validate -============== +-------------- The [dlite-validate tool][./tools.md#dlite_validate] can be used to check if a specific representation (in a file) is a valid DLite datamodel diff --git a/doc/user_guide/storage_plugins.md b/doc/user_guide/storage_plugins.md index c45d147f0..d9eb0d9df 100644 --- a/doc/user_guide/storage_plugins.md +++ b/doc/user_guide/storage_plugins.md @@ -4,10 +4,11 @@ Storage plugins / Drivers Content ------- 1. [Introduction](#introduction) - 2. [Working with storages in Python](#working-with-storages-in-python) - 3. [Using storages implicitely](#using-storages-implicitly) - 4. [Writing Python storage plugins](#writing-python-storage-plugins) - 5. [Working with storages from C and Fortran](#working-with-storages-from-c-and-fortran) + 2. [How to make storage plugins available](#how-to-make-storage-plugins-available) + 3. [Using storages implicitely from Python](#using-storages-implicitly-from-python) + 4. [Working with storages in Python](#working-with-storages-in-python) + 5. [Writing Python storage plugins](#writing-python-storage-plugins) + 6. [Working with storages from C and Fortran](#working-with-storages-from-c-and-fortran) Introduction @@ -30,7 +31,6 @@ Storage plugins can be written in either C or Python. How to make storage plugins available ------------------------------------- - As described below it is possible (and most often advisable) to create specific drivers (storage plugins) for your data. Additional storage plugins drivers can be made available by setting the environment variables `DLITE_STORAGE_PLUGIN_DIRS` or `DLITE_PYTHON_STORAGE_PLUGIN_DIRS` e.g.: @@ -45,15 +45,15 @@ import dlite dlite.python_storage_plugin_path.append("/path/to/plugins/dir") ``` -Often drivers are connected to very specific datamodel (entities). +Often drivers are connected to very specific datamodel (entities). DLite will find these datamodels if the path to their directory is set with the environment variable `DLITE_STORAGES` or added within python with `dlite.storage_path.append` similarly to described above for drivers. ```{attention} -Often, during development dlite will fail unexpectedly. This is typically either because of an error in the -datamodel or the driver. -The variable DLITE_PYDEBUG can be set as `export DLITE_PYDEBUG=` to get python debugging information. +Often, during development dlite will fail unexpectedly. This is typically either because of an error in the +datamodel or the driver. +The variable DLITE_PYDEBUG can be set as `export DLITE_PYDEBUG=` to get python debugging information. This will give information about the driver. It is advisable to first check that the datamodel is valid with the command `dlite-validate datamodelfilename`. ```