#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""An example script that uses EMMO to describe a vertical use case on
welding aluminium to steel and how the thin layer of intermetallic
that are formed at the interface is influencing the overall
properties.  Based on TEM observations using scanning precision
electron diffraction (SPED) the following (simplified) sequence of
intermetallic phases could be established:

    Al | alpha-AlFeSi | Fe4Al13 | Fe2Al5 | Fe

which is consistent with phase stability when the Fe-concentration is
increasing when going from left to right.

In this case study three scales are considered:

  - Macroscopic scale: predicts the overall mechanical behaviour of
    the welded structure during deformation.

  - Microscopic scale: a local crystal plasticity model of a small
    part of the interface.  The constitutive equations were based on
    the results from DFT.  The results from this model was used to
    calibrate decohesion elements for the macroscopic scale.

  - Electronic scale: elastic properties of the individual phases as
    well work of decohesion within and at the interfaces between the
    phases were calculated with DFT [1].  The calculation of work of
    decohesion was performed as a series of rigid steps, providing
    stress-strain relations in both tensile and shear.

References
----------
[1] Khalid et al. Proc. Manufact. 15 (2018) 1407

"""

# pylint: disable=fixme,invalid-name,too-few-public-methods
from ontopy import World

# Load EMMO
world = World(filename="demo.sqlite3")
emmo = world.get_ontology(
    "https://raw.githubusercontent.com/emmo-repo/EMMO/master/emmo.ttl"
)
emmo.load()
# emmo.sync_reasoner()

# Create a new ontology with out extensions that imports EMMO
onto = world.get_ontology("http://www.emmc.info/emmc-csa/demo#")
onto.imported_ontologies.append(emmo)


# Add new classes and object/data properties needed by the use case
with onto:
    #
    # Relations
    # =========
    class hasType(emmo.hasConvention):
        """Associates a type (string, number...) to a property."""

    class isTypeOf(emmo.hasConvention):
        """Associates a property to a type (string, number...)."""

        inverse_property = hasType

    #
    # Units
    # =====

    # TODO: remove
    class SquareLengthDimension(emmo.PhysicalDimension):
        """Squared length dimension."""

        is_a = [emmo.hasSymbolData.value("T0 L2 M0 I0 Θ0 N0 J0")]

    # TODO: remove
    class SquareMetre(emmo.SICoherentDerivedUnit):
        """A square metre unit."""

        emmo.altLabel = ["m²"]
        is_a = [emmo.hasPhysicalDimension.only(SquareLengthDimension)]

    #
    # Properties
    # ==========

    # TODO: update instead of redefine Position
    class Position(emmo.Length):
        """Spatial position of an physical entity."""

        is_a = [
            emmo.hasReferenceUnit.only(
                emmo.hasPhysicalDimension.only(emmo.LengthDimension)
            ),
            hasType.exactly(3, emmo.Real),
        ]

    # TODO: remove
    class Area(emmo.ISQDerivedQuantity):
        """Extent of a surface."""

        is_a = [
            emmo.hasReferenceUnit.only(
                emmo.hasPhysicalDimension.only(SquareLengthDimension)
            ),
            hasType.exactly(1, emmo.Real),
        ]

    emmo.Pressure.is_a.append(hasType.exactly(1, emmo.Real))

    # TODO: update when we have dimensionality
    class StiffnessTensor(emmo.Pressure):
        r"""The stiffness tensor $c_{ijkl}$ is a property of a continuous
        elastic material that relates stresses to strains (Hooks's
        law) according to

            $\sigma_{ij} = c_{ijkl} \epsilon_{kl}$

        Due to symmetry and using the Voight notation, the stiffness
        tensor can be represented as a symmetric 6x6 matrix

            / c_1111  c_1122  c_1133  c_1123  c_1131  c_1112 \
            | c_2211  c_2222  c_2233  c_2223  c_2231  c_2212 |
            | c_3311  c_3322  c_3333  c_3323  c_3331  c_3312 |
            | c_2311  c_2322  c_2333  c_2323  c_2331  c_2312 |
            | c_3111  c_3122  c_3133  c_3123  c_3131  c_3112 |
            \ c_1211  c_1222  c_1233  c_1223  c_1231  c_1212 /

        """

        is_a = [hasType.exactly(36, emmo.Real)]

    # class Spacegroup(emmo.DescriptiveProperty):
    #     """A spacegroup is the symmetry group off all symmetry operations
    #     that apply to a crystal structure.
    #
    #     It is identifies by its Hermann-Mauguin symbol or space group
    #     number (and setting) in the International tables of
    #     Crystallography."""
    #     is_a = [hasType.exactly(1, emmo.String)]
    #     pass

    # class Plasticity(emmo.PhysicalQuantity):
    #     """Describes Yield stress and material hardening."""
    #     is_a = [hasUnit.exactly(1, Pascal),
    #             hasType.min(2, emmo.Real)]

    # Will be included when dimensionality is in place in EMMO.

    # class TractionSeparation(Pressure):
    #     """The force required to separate two materials a certain distance
    #     per interface area.  Hence, traction_separation is a curve, that
    #     numerically can be represented as a series of (force,
    #     separation_distance) pairs."""
    #     is_a = [hasUnit.exactly(1, Pascal),
    #             hasType.min(4, emmo.Real)]

    # class LoadCurve(Pressure):
    #     """A measure for the displacement of a material as function of the
    #     appliced force."""
    #     is_a = [hasUnit.exactly(1, Pascal),
    #             hasType.min(4, emmo.Real)]

    # Crystallography-related classes
    # TODO: import crystallography ontology instead
    # -------------------------------
    class LatticeVector(emmo.Length):
        """A vector that participitates defining the unit cell."""

        is_a = [hasType.exactly(3, emmo.Real)]

    # FIXME - CrystalUnitCell is not a matter, but a model or a symbolic
    #         Just use crystalography
    class CrystalUnitCell(emmo.Material):
        """A volume defined by the 3 unit cell vectors.  It contains the atoms
        constituting the unit cell of a crystal."""

        is_a = [
            emmo.hasSpatialDirectPart.some(emmo.BondedAtom),
            emmo.hasProperty.exactly(3, LatticeVector),
            emmo.hasProperty.exactly(1, StiffnessTensor),
        ]

    class InterfaceModel(CrystalUnitCell):
        """A crystal interface."""

        is_a = [emmo.hasProperty.some(Area)]

    class Crystal(emmo.Solid):
        """A periodic crystal structure."""

        is_a = [emmo.hasSpatialDirectPart.only(CrystalUnitCell)]

    # Add some properties to our atoms
    emmo.Atom.is_a.append(emmo.hasProperty.exactly(1, Position))

    # Continuum
    # ---------
    class Boundary(emmo.Continuum):
        """A boundary is a 4D region of spacetime shared by two material
        entities."""

        equivalent_to = [emmo.hasSpatialDirectPart.exactly(2, emmo.Continuum)]
        is_a = [emmo.hasProperty.exactly(1, Area)]

    class Phase(emmo.Continuum):
        """A phase is a continuum in which properties are homogeneous and can
        have different state of matter."""

        is_a = [emmo.hasProperty.exactly(1, StiffnessTensor)]

    class RVE(emmo.Continuum):
        """Representative volume element.  The minimum volume that is
        representative for the system in question."""

        is_a = [
            emmo.hasSpatialDirectPart.only(
                Phase | Boundary  # pylint: disable=unsupported-binary-operation
            )
        ]

    class WeldedComponent(emmo.EngineeredMaterial):
        """A welded component consisting of two materials welded together
        using a third welding material.  Hence it has spatial direct
        parts 3 materials and two boundaries."""

        is_a = [
            emmo.hasSpatialDirectPart.exactly(3, emmo.Material),
            emmo.hasSpatialDirectPart.exactly(2, Boundary),
            # emmo.hasProperty.exactly(1, LoadCurve),
        ]


# Sync attributes to make sure that all classes get a `label` and to
# include the docstrings in the comments
onto.sync_attributes(name_policy="uuid", name_prefix="DEMO_")


# Run the reasoner
# onto.sync_reasoner()

# set version of ontology
onto.set_version("0.9")

# Save our new EMMO-based ontology to demo.owl
onto.save("demo.owl", overwrite=True)

# ...and to the sqlite3 database.
world.save()
